Conversation
- Add typed EventBus class with before/after event patterns - Define plugin type interfaces (PluginUser, PluginNote, PluginFollow, PluginActivity) - Use Visibility union type for type safety in note visibility - Implement event payload types for all lifecycle events - Add note lifecycle events: beforeCreate, afterCreate, beforeDelete, afterDelete - Add user lifecycle events: beforeRegister, afterRegister, beforeLogin, afterLogin, afterLogout - Add follow lifecycle events: afterCreate, afterDelete - Add ActivityPub events: beforeInbox, afterInbox, beforeDelivery, afterDelivery - Add moderation events: userSuspended, noteDeleted - Integrate EventBus into DI container and middleware - Emit events in NoteService, AuthService, FollowService - Apply modifiedPayload from beforeCreate/beforeRegister events - Remove PII (email) from user:beforeRegister payload - Remove PII (ipAddress, userAgent) from user:beforeLogin payload - Add shared toPluginUser/toPluginNote utility functions - Add test helper factory functions - Document composite followId format for delete events - Document emitBefore error handling behavior
- Add PluginLoader class for plugin discovery and lifecycle management - Implement DatabasePluginConfigStorage for persistent config - Add plugin routes for admin API - Support both array and object formats for plugin dependencies - Provide proper PluginContext to ActivityPub handlers - Fix TypeScript errors in test files
…plugin (#138) Frontend Plugin System: - Add PluginRegistry singleton for managing plugin state - Create PluginSlot component for rendering plugin-provided components - Define slot types (19 locations: note, compose, profile, settings, admin, etc.) - Export public API from lib/plugins/index.ts Admin Plugin Management: - Add /admin/plugins page for viewing and managing plugins - Enable/disable plugins through admin interface - Display plugin status and version - Add Plugins link to AdminNav Sample Plugin: - Add hello-world sample plugin demonstrating: - Plugin manifest (plugin.json) - Event subscriptions (note:afterCreate) - Custom API routes (/api/x/hello-world/) - Configuration storage - Add plugins/README.md with development documentation - Add comment explaining context unavailability in onUnload Backend Updates: - Add plugin routes at /api/plugins - Add enabled check middleware for plugin routes (404 when disabled) - Add enabled check for ActivityPub handlers - Fix logger signature to accept variadic args - Add manifest.id vs plugin.id mismatch validation - Add PII comment for afterLogin event - Remove sensitive fields (error, loadedAt) from public plugin endpoints - Add description to plugin list API response README Documentation: - Update Phase 7 status to Complete - Add Plugin System feature description - Add Plugin Development documentation link - Fix slot count (19 locations)
Frontend: - waku: 1.0.0-alpha.2 → 1.0.0-alpha.3 - jotai: 2.16.0 → 2.17.0 - @lingui/*: 5.7.0 → 5.9.0 - react/react-dom: 19.2.3 → 19.2.4 - react-hook-form: 7.68.0 → 7.71.1 - lucide-react: 0.561.0 → 0.563.0 - katex: 0.16.27 → 0.16.28 Backend: - hono: 4.11.1 → 4.11.7 - zod: 4.2.1 → 4.3.6 - pino: 10.1.0 → 10.3.0 - pg: 8.16.3 → 8.17.2 - ioredis: 5.8.2 → 5.9.2 - bullmq: 5.66.1 → 5.67.2 - @aws-sdk/client-s3: 3.954.0 → 3.975.0 - mysql2: 3.16.0 → 3.16.2 - better-sqlite3: 12.5.0 → 12.6.2 Dev Tools: - oxlint: 1.33.0 → 1.42.0 - @playwright/test: 1.57.0 → 1.58.0 - @types/bun: 1.3.4 → 1.3.7 - typedoc: 0.28.15 → 0.28.16 - vitest: 4.0.16 → 4.0.18 Jotai v2.17.0 deprecates loadable, setSelf, and removes unstable_onInit, but these are not used in our codebase.
📝 WalkthroughWalkthroughバックエンドとフロントエンドにプラグインシステムを追加。ローダー、永続設定ストレージ、初期化/ミドルウェア、管理API、フロントエンドのスロット・レジストリ、管理UI、ドキュメント、サンプルプラグイン、単体テストを含む実装です。(50語以内) Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Frontend as FrontendApp
participant Backend as Backend
participant PluginSys as PluginSystem
participant DB as Database
participant PluginCode as PluginCode
rect rgba(100, 200, 255, 0.5)
Note over Backend,PluginSys: 起動時プラグイン初期化
Backend->>PluginSys: initializePluginSystem(options)
PluginSys->>PluginSys: scan plugins ディレクトリ / read manifests
PluginSys->>PluginCode: import plugin index.js
PluginCode->>PluginSys: onLoad(context)
PluginSys->>DB: init/config storage (plugin_configs)
PluginSys-->>Backend: return PluginSystem (loader, routes, shutdown)
end
rect rgba(100, 255, 100, 0.5)
Note over Client,Backend: 管理API操作
Client->>Backend: GET /api/plugins or POST /api/plugins/:id/enable
Backend->>PluginSys: query loader / setEnabled(pluginId)
PluginSys-->>Backend: status/result
Backend-->>Client: JSON レスポンス
end
rect rgba(255, 200, 100, 0.5)
Note over Frontend,PluginCode: フロントエンド描画フロー
Frontend->>Frontend: PluginSlot が registry を参照
Frontend->>PluginCode: render slot component(s) (ErrorBoundary/Suspense)
PluginCode-->>Frontend: JSX を返す
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 分 Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@packages/backend/src/index.ts`:
- Around line 202-210: The initializePluginSystem call is missing the persistent
database, causing plugin settings to be saved only in memory; call getDatabase()
to obtain the db and pass it as the db parameter into initializePluginSystem
(inside the same pluginsEnabled block where initializePluginSystem is called),
e.g., retrieve db via getDatabase() and include db: db in the options passed to
initializePluginSystem so plugin state persists across restarts.
- Around line 194-200: Move the pluginLoader middleware (the app.use("*", async
(c, next) => { if (pluginSystem) { c.set("pluginLoader", pluginSystem.loader); }
await next(); })) so it is registered with the other global middlewares (near
the metrics/global middlewares) instead of after route definitions; this ensures
routes in routes/plugins.ts can reliably read c.get("pluginLoader") without
needing defensive checks — locate the existing pluginLoader middleware and
re-register it earlier in the bootstrap sequence alongside the global
middlewares that run before any route handlers.
In `@packages/backend/src/plugins/config-storage.ts`:
- Around line 86-100: DatabasePluginConfigStorage defines getAll and setMultiple
but IPluginConfigStorage doesn't and there are no call sites; either add these
methods to the IPluginConfigStorage interface (declare getAll(pluginId: string):
Promise<Record<string, unknown>> and setMultiple(pluginId: string, entries:
Record<string, unknown>): Promise<void>) so the implementation conforms, or
remove getAll and setMultiple from the DatabasePluginConfigStorage class (and
any related helper code) if they are unused; if you keep them, also update any
callers or add unit tests to exercise getAll/setMultiple so their presence is
justified.
In `@packages/frontend/package.json`:
- Around line 19-38: The package.json lists a non-existent Waku version; update
the "waku" dependency (symbol: "waku") to a valid published version such as
"1.0.0-alpha.2" (or a caret range like "^1.0.0-alpha.2") and run your package
manager to lock the correct version; also verify the listed "jotai" and "react"
versions (symbols: "jotai", "react") are intentionally chosen—keep Jotai 2.17.0
only if you accept the deprecation warnings (loadable and setSelf in atom read)
and keep React 19.2.4 if you only want the patch release, otherwise adjust
accordingly.
🧹 Nitpick comments (11)
packages/frontend/src/lib/plugins/registry.ts (1)
188-197:useSyncExternalStoreとの組み合わせで参照安定性の問題が発生する可能性があります。
getSlotComponentsは呼び出しごとに新しい配列を生成するため、useSyncExternalStoreのスナップショット比較で常に変更があると判定され、不要な再レンダリングが発生する可能性があります。現時点で実用上の問題がなければ後回しでも構いませんが、パフォーマンスに影響が出た場合はキャッシュ機構の導入を検討してください。
💡 キャッシュ機構の例
// クラスにキャッシュフィールドを追加 private slotComponentsCache: Map<SlotName, { version: number; result: SlotEntry[] }> = new Map(); private cacheVersion = 0; // notifyListeners() 内でバージョンをインクリメント private notifyListeners(): void { this.cacheVersion++; // ... existing code } // getSlotComponents でキャッシュを活用 getSlotComponents<T extends SlotName>(slotName: T): SlotEntry<T>[] { const cached = this.slotComponentsCache.get(slotName); if (cached && cached.version === this.cacheVersion) { return cached.result as SlotEntry<T>[]; } // ... compute result this.slotComponentsCache.set(slotName, { version: this.cacheVersion, result }); return result as SlotEntry<T>[]; }packages/frontend/src/lib/plugins/PluginSlot.tsx (1)
120-129: 同一プラグインが同一スロットに複数コンポーネントを登録した場合、キーが重複します。
pluginIdをキーとして使用していますが、プラグインが同一スロットに複数のコンポーネントを登録する場合、React のキー重複警告が発生します。現在の
types.tsの定義で1プラグイン1スロットに1コンポーネントのみを想定しているのであれば問題ありませんが、将来の拡張性のためにインデックスを含めたキーの使用を検討してください。💡 キー重複回避の例
- const rendered = components.map(({ pluginId, component }) => { + const rendered = components.map(({ pluginId, component }, index) => { const DynamicComponent = component as unknown as React.ComponentType<Record<string, unknown>>; return ( - <PluginErrorBoundary key={pluginId} pluginId={pluginId}> + <PluginErrorBoundary key={`${pluginId}-${index}`} pluginId={pluginId}> <Suspense fallback={<PluginLoading />}> <DynamicComponent {...props} pluginId={pluginId} /> </Suspense> </PluginErrorBoundary> ); });packages/backend/src/db/schema/pg.ts (1)
936-955: 複合主キーの使用でid導出値の冗長性を解決してください。現在の設計では、
idフィールドが{pluginId}:{key}形式の派生値として保守され、同時に(pluginId, key)の一意インデックスが存在します。これは二重管理となっており、id形式が正しくない場合でもデータベース制約では検出できないリスクがあります。Drizzle ORM は複合主キーをサポートしているため、以下のように設計を変更することをお勧めします:
primaryKey({ columns: [table.pluginId, table.key] })この方式により、
id派生値を削除し、(pluginId, key)を直接主キーにすることで、整合性を強制でき、コード上の導出ロジックの負担を軽減できます。packages/frontend/src/pages/admin/plugins.tsx (1)
27-39: 型定義の重複について確認が必要です。
PluginとPluginSystemStatusインターフェースはpackages/frontend/src/lib/plugins/types.tsで既に定義されているPluginManifestResponseとPluginSystemStatusと重複しています。一貫性と保守性のため、既存の型定義をインポートして使用することを検討してください。♻️ 提案される修正
-interface Plugin { - id: string; - name: string; - version: string; - description?: string; - enabled: boolean; -} - -interface PluginSystemStatus { - enabled: boolean; - loaded: number; - plugins: Plugin[]; -} +import type { PluginSystemStatus } from "../../lib/plugins/types";plugins/hello-world/index.js (1)
29-37: サンプルプラグインでのユーザー名ログ出力について。
payload.author.usernameをログに記録していますが、これはPII(個人識別情報)に該当する可能性があります。サンプルプラグインとして、ユーザー名の代わりに匿名化された情報(例:ユーザーIDの一部やイベント発生回数のみ)をログに記録することを検討してください。♻️ 提案される修正
context.events.on( "note:afterCreate", (payload) => { context.logger.info( - `New note created by @${payload.author.username} (${payload.note.text?.length || 0} chars)`, + `New note created (${payload.note.text?.length || 0} chars)`, ); }, "hello-world", );packages/backend/src/routes/plugins.ts (1)
92-107:requireAuth()とrequireAdmin()の冗長な使用について。
requireAdmin()ミドルウェアは内部で認証チェックを行っているため(packages/backend/src/middleware/auth.tsの実装を参照)、requireAuth()の呼び出しは冗長です。ただし、明示的な意図の表現として残すことも許容されます。♻️ 提案される修正(オプション)
-plugins.post("/:id/enable", requireAuth(), requireAdmin(), async (c: Context) => { +plugins.post("/:id/enable", requireAdmin(), async (c: Context) => {packages/backend/src/plugins/loader.ts (1)
456-461:package.jsonのdependenciesフィールド解析について。
package.jsonのdependenciesフィールドをObject.keys()で解析していますが、これにはnpmパッケージ(hono、zodなど)も含まれる可能性があります。プラグイン依存関係専用のフィールド(例:roxDependenciesやpluginDependencies)を使用することを検討してください。♻️ 提案される修正
dependencies: json.dependencies ? Array.isArray(json.dependencies) ? json.dependencies - : Object.keys(json.dependencies) + : undefined // Prefer explicit pluginDependencies field : undefined, + // Or add support for roxDependencies field: + // dependencies: json.roxDependencies || json.pluginDependencies,packages/backend/src/plugins/init.ts (4)
43-50:configStorageをPluginSystemインターフェースに追加することを検討してください
registerPluginActivityPubHandlersはActivityPubHandlerOptionsでconfigStorageを必要としますが、PluginSystemインターフェースはそれを公開していません。これにより、initializePluginSystemの呼び出し元が ActivityPub ハンドラーを登録する際にconfigStorageを別途管理する必要があります。♻️ 提案する修正
export interface PluginSystem { /** Plugin loader */ loader: PluginLoader; /** Plugin routes Hono sub-app */ routes: Hono; + /** Config storage for plugin configuration */ + configStorage: IPluginConfigStorage; /** Shutdown function */ shutdown: () => Promise<void>; }そして、
initializePluginSystemの戻り値でもconfigStorageを含めてください:return { loader, routes: pluginRoutes, + configStorage, shutdown: async () => {
152-154:as never型アサーションは型安全性を損ないます
as neverキャストは TypeScript の型チェックを完全にバイパスします。これは実行時エラーを隠す可能性があります。ミドルウェアの型を適切に定義するか、Hono の
MiddlewareHandler型を使用することを検討してください。♻️ 提案する修正
+import type { MiddlewareHandler } from "hono";- app.use("*", mw as never); + app.use("*", mw as MiddlewareHandler);または、
getPluginsWithMiddlewareの戻り値の型をMiddlewareHandler[]に変更することで、キャストを不要にできます。
237-252: プラグインコンテキスト作成ロジックが重複していますこのコンテキスト作成ロジックは
PluginLoader.createPluginContext()と重複しています。将来的に片方だけ更新され、動作の不整合が生じる可能性があります。
PluginLoaderにコンテキスト作成メソッドを公開するか、共通のヘルパー関数を抽出することを検討してください。
254-263: 複合ハンドラーは常に成功を返しますが、エラーを隠す可能性があります個々のハンドラーがエラーをスローしても、複合ハンドラーは常に
{ success: true }を返します。これはフォールトトレランスとしては良いですが、呼び出し元が部分的な失敗を認識できません。部分的な失敗を報告するために、失敗したハンドラーの情報を戻り値に含めることを検討してください。
♻️ 提案する修正
+ const errors: Array<{ pluginId: string; error: string }> = []; for (const { pluginId, handler } of pluginHandlers) { // Skip disabled plugins if (!loader.getPlugin(pluginId)?.enabled) { continue; } try { // ... existing code ... await handler(activity as PluginActivity, pluginContext); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; pluginLogger.error( { pluginId, activityType, error: message }, "Plugin ActivityPub handler error", ); + errors.push({ pluginId, error: message }); // Continue with other handlers even if one fails } } - return { success: true }; + return { + success: errors.length === 0, + message: errors.length > 0 ? `${errors.length} handler(s) failed` : undefined, + };
- Move pluginLoader middleware before routes for proper execution order - Add db parameter to initializePluginSystem for persistent config storage - Remove unused getAll/setMultiple methods from DatabasePluginConfigStorage - Fix Database type to use NodePgDatabase instead of PostgresJsDatabase
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@packages/backend/src/index.ts`:
- Around line 203-225: The plugin system is initialized asynchronously with
initializePluginSystem(...) which can cause race conditions because the server
(and its routes like /api/plugins and /api/x/*) may start before pluginSystem is
ready; change this to synchronous initialization by awaiting
initializePluginSystem (assign to pluginSystem) before continuing to start the
server or print the startup banner, wrapping the surrounding module entrypoint
in an async function or using top-level await as appropriate; keep the existing
try/catch around the await and retain the logger.info/logger.error calls
(referencing pluginSystem, initializePluginSystem, logger.info, logger.error) so
the server only proceeds after the plugin loader is fully initialized.
In `@packages/backend/src/plugins/init.ts`:
- Around line 150-160: The code is casting middleware items to never because
loader.getPluginsWithMiddleware() returns unknown[]; change the return type of
getPluginsWithMiddleware in packages/backend/src/plugins/loader.ts from
unknown[] to MiddlewareHandler[] so the array items are correctly typed, then
remove the unnecessary cast in init.ts (where pluginsWithMiddleware is iterated
and mw is passed to app.use). Ensure you import/retain the MiddlewareHandler
type in both loader.ts and init.ts and keep pluginId/middleware structure
unchanged so pluginLogger.debug/error logic remains valid.
- Around line 222-265: The composite handler always returns { success: true }
even when individual plugin handlers fail, which causes
InboxService.recordInboxActivity() to misreport metrics; update the
compositeHandler.handle implementation (the async handle function inside the
handlers loop) to track plugin handler failures (e.g., count errors or collect
messages) and, after iterating pluginHandlers, return success: false when any
plugin threw an error and include an errors or failureCount field with details;
ensure you still catch and log per-plugin errors via pluginLogger but propagate
aggregate failure info in the returned object so callers like
InboxService.recordInboxActivity() can record accurate metrics.
🧹 Nitpick comments (2)
packages/backend/src/plugins/config-storage.ts (2)
31-44: 型安全性に関する軽微な懸念
row.value as Tのキャストは、データベースから取得した値が実際にT型であることを保証しません。プラグインが不正な型の値を保存した場合、ランタイムエラーが発生する可能性があります。現状の実装は多くのORMストレージで採用されている一般的なパターンであり、プラグインAPIの使用契約として許容できますが、将来的にスキーマバリデーション(例:Zodとの統合)を検討する価値があります。
49-68:new Date()の2回呼び出しによるタイムスタンプの微小なずれ
updatedAtが59行目と65行目で2回new Date()を呼び出しています。通常は問題になりませんが、厳密にはINSERTとUPDATE時で異なるタイムスタンプになる可能性があります。♻️ 提案される修正
async set<T>(pluginId: string, key: string, value: T): Promise<void> { const id = `${pluginId}:${key}`; + const now = new Date(); await this.db .insert(pluginConfigs) .values({ id, pluginId, key, value: value as unknown, - updatedAt: new Date(), + updatedAt: now, }) .onConflictDoUpdate({ target: pluginConfigs.id, set: { value: value as unknown, - updatedAt: new Date(), + updatedAt: now, }, }); }
| for (const { pluginId, middleware } of pluginsWithMiddleware) { | ||
| try { | ||
| for (const mw of middleware) { | ||
| app.use("*", mw as never); | ||
| } | ||
| pluginLogger.debug({ pluginId, count: middleware.length }, "Registered plugin middleware"); | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : "Unknown error"; | ||
| pluginLogger.error({ pluginId, error: message }, "Failed to register plugin middleware"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check plugin types and middleware definitions
rg -nA 20 'middleware' packages/backend/src/plugins/types.ts packages/backend/src/plugins/loader.ts 2>/dev/null | head -100Repository: Love-Rox/rox
Length of output: 3566
🏁 Script executed:
#!/bin/bash
# Check the actual init.ts file around the problematic lines
sed -n '140,165p' packages/backend/src/plugins/init.tsRepository: Love-Rox/rox
Length of output: 996
🏁 Script executed:
#!/bin/bash
# Check Hono app.use() type definition to understand expected middleware type
rg -nB 5 -A 10 'app\.use.*\*' packages/backend/src/plugins/init.tsRepository: Love-Rox/rox
Length of output: 708
🏁 Script executed:
#!/bin/bash
# Check MiddlewareHandler definition and its import
rg -nB 5 -A 5 'MiddlewareHandler' packages/backend/src/plugins/types.ts | head -50Repository: Love-Rox/rox
Length of output: 691
🏁 Script executed:
#!/bin/bash
# Check imports in types.ts to understand MiddlewareHandler origin
head -50 packages/backend/src/plugins/types.ts | grep -E '^import|^export|MiddlewareHandler'Repository: Love-Rox/rox
Length of output: 260
🏁 Script executed:
#!/antml/bash
# Check why loader.ts uses unknown[] instead of MiddlewareHandler[]
sed -n '375,395p' packages/backend/src/plugins/loader.tsRepository: Love-Rox/rox
Length of output: 630
ミドルウェアの型を MiddlewareHandler で明確に定義してください
Line 153 の mw as never キャストが必要な理由は、loader.getPluginsWithMiddleware() が unknown[] 型で返しているためです。プラグイン型定義では正しく MiddlewareHandler[] として定義されていますが、loader でわざと型を広げられています。
この型の不一致を解決するには、packages/backend/src/plugins/loader.ts の getPluginsWithMiddleware() メソッドの戻り値型を unknown[] から MiddlewareHandler[] に変更し、init.ts のキャストを削除してください。
🤖 Prompt for AI Agents
In `@packages/backend/src/plugins/init.ts` around lines 150 - 160, The code is
casting middleware items to never because loader.getPluginsWithMiddleware()
returns unknown[]; change the return type of getPluginsWithMiddleware in
packages/backend/src/plugins/loader.ts from unknown[] to MiddlewareHandler[] so
the array items are correctly typed, then remove the unnecessary cast in init.ts
(where pluginsWithMiddleware is iterated and mw is passed to app.use). Ensure
you import/retain the MiddlewareHandler type in both loader.ts and init.ts and
keep pluginId/middleware structure unchanged so pluginLogger.debug/error logic
remains valid.
- Use top-level await for plugin initialization to avoid race conditions - Fix middleware type in loader.ts (unknown[] → MiddlewareHandler[]) - Remove unnecessary type cast in init.ts - Track plugin handler errors in compositeHandler for accurate metrics
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/backend/src/plugins/init.ts`:
- Around line 148-159: Registered plugin middleware is never skipped after a
plugin is disabled because app.use("*", mw) persists; wrap each middleware with
a runtime enabled check before registering so the wrapper queries the plugin's
current enabled state (e.g., via loader.getPlugin(pluginId)?.isEnabled() or an
equivalent runtime check) and only invokes the original mw when enabled; replace
direct app.use("*", mw) with app.use("*", (req, res, next) => { if
(pluginEnabledCheck(pluginId)) return mw(req, res, next); return next(); }) and
keep existing error logging around registration using pluginId and middleware
references.
In `@packages/backend/src/plugins/loader.ts`:
- Around line 432-470: The loadManifest function currently converts object-form
dependencies from package.json into plugin IDs; update it so that when reading
package.json (detect via the current path variable or manifestPaths), if
json.dependencies is an object, ignore it (set dependencies undefined) and only
accept an array-form dependencies (and for plugin.json continue to accept an
array), so we don't treat npm package names as plugin IDs; adjust the
dependencies assignment accordingly in loadManifest (use the path/file name
check and only map Array.isArray(json.dependencies) => json.dependencies,
otherwise undefined).
packages/backend/src/plugins/init.ts
Outdated
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "init.ts" -path "*/plugins/*" | head -20Repository: Love-Rox/rox
Length of output: 94
🏁 Script executed:
cat -n packages/backend/src/plugins/init.ts | sed -n '140,170p'Repository: Love-Rox/rox
Length of output: 1305
🏁 Script executed:
# Find the plugin loader and understand the getPlugin and setEnabled methods
rg -A 5 "setEnabled|getPlugin" packages/backend/src/plugins/ -t tsRepository: Love-Rox/rox
Length of output: 4155
🏁 Script executed:
# Check for any existing middleware filtering or plugin state checks
rg -B 3 -A 3 "getPluginsWithMiddleware|middleware.*enabled" packages/backend/src/plugins/ -t tsRepository: Love-Rox/rox
Length of output: 1125
プラグインミドルウェアを無効化時に実行させないようにする
setEnabled(false) 後もミドルウェアが実行され続けます。getPluginsWithMiddleware() は有効なプラグインのみを返しますが、一度 app.use("*", mw) で登録されたミドルウェアは残ります。ルート (routes) にはランタイムの enabled チェックが実装されていますが、ミドルウェアにはありません。ミドルウェア登録時に enabled チェック付きラッパーで保護してください。
修正案
for (const mw of middleware) {
- app.use("*", mw);
+ app.use("*", async (c, next) => {
+ if (!loader.getPlugin(pluginId)?.enabled) {
+ return next();
+ }
+ return mw(c, next);
+ });
}🤖 Prompt for AI Agents
In `@packages/backend/src/plugins/init.ts` around lines 148 - 159, Registered
plugin middleware is never skipped after a plugin is disabled because
app.use("*", mw) persists; wrap each middleware with a runtime enabled check
before registering so the wrapper queries the plugin's current enabled state
(e.g., via loader.getPlugin(pluginId)?.isEnabled() or an equivalent runtime
check) and only invokes the original mw when enabled; replace direct
app.use("*", mw) with app.use("*", (req, res, next) => { if
(pluginEnabledCheck(pluginId)) return mw(req, res, next); return next(); }) and
keep existing error logging around registration using pluginId and middleware
references.
- Wrap plugin middleware with runtime enabled check to skip when disabled - Fix loadManifest to ignore npm object-form dependencies in package.json (only array-form plugin IDs are accepted from package.json)
Summary
Update all dependencies to latest versions.
Changes
Frontend
Backend
Dev Tools
Notes
loadable,setSelf, and removesunstable_onInit. These APIs are not used in our codebase, so no migration is needed.Test Plan
Summary by CodeRabbit
New Features
Backend
Frontend
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.