From 27ec0b4cd73fe9173bc8d49ddde6414e3aac88df Mon Sep 17 00:00:00 2001 From: Guy Ben Aharon Date: Mon, 23 Feb 2026 13:10:45 +0200 Subject: [PATCH 1/2] fix: lazy plugin loading + pino logger --- apps/web/Dockerfile | 15 +++++ .../_components/api-keys-settings.tsx | 11 +--- .../src/app/api/auth/[...nextauth]/route.ts | 22 ++++++- apps/web/src/app/api/squadhub/health/route.ts | 5 +- .../web/src/app/api/tenant/provision/route.ts | 59 ++++++++++++++++++- .../app/api/tenant/squadhub/restart/route.ts | 5 +- apps/web/src/app/api/tenant/squadhub/route.ts | 5 +- .../app/api/tenant/squadhub/status/route.ts | 5 +- apps/web/src/app/api/tenant/status/route.ts | 5 +- apps/web/src/lib/logger.ts | 19 ++++++ apps/web/src/lib/plugins.ts | 26 ++++++++ packages/plugins/src/cloud-plugins.d.ts | 7 ++- packages/plugins/src/index.ts | 5 +- packages/plugins/src/interfaces/index.ts | 2 + packages/plugins/src/interfaces/logger.ts | 9 +++ packages/plugins/src/registry.spec.ts | 15 +++-- packages/plugins/src/registry.ts | 30 ++++------ turbo.json | 18 +++++- 18 files changed, 205 insertions(+), 58 deletions(-) create mode 100644 apps/web/src/lib/logger.ts create mode 100644 apps/web/src/lib/plugins.ts create mode 100644 packages/plugins/src/interfaces/logger.ts diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 1081449..aa8b6af 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -33,6 +33,11 @@ COPY --from=deps /app/ . COPY --from=pruner /app/out/full/ . RUN pnpm build --filter=@clawe/web +# Deploy cloud-plugins with resolved deps (cloud edition only) +RUN if [ "$NEXT_PUBLIC_CLAWE_EDITION" = "cloud" ]; then \ + pnpm --filter=@clawe/cloud-plugins deploy /app/cloud-plugins-deploy --prod; \ + fi + # Production runner FROM node:22-alpine AS runner WORKDIR /app @@ -52,6 +57,16 @@ COPY --from=builder --chown=node:node /app/apps/web/.next/standalone ./ COPY --from=builder --chown=node:node /app/apps/web/.next/static ./apps/web/.next/static COPY --from=builder --chown=node:node /app/apps/web/public ./apps/web/public +# Cloud-plugins runtime deps (cloud edition only) +ARG NEXT_PUBLIC_CLAWE_EDITION +RUN --mount=from=builder,source=/app,target=/tmp/builder \ + if [ "$NEXT_PUBLIC_CLAWE_EDITION" = "cloud" ]; then \ + mkdir -p ./node_modules/@clawe/cloud-plugins && \ + cp /tmp/builder/cloud-plugins-deploy/package.json ./node_modules/@clawe/cloud-plugins/ && \ + cp -r /tmp/builder/cloud-plugins-deploy/dist ./node_modules/@clawe/cloud-plugins/ && \ + cp -r /tmp/builder/cloud-plugins-deploy/node_modules/. ./node_modules/; \ + fi + USER node EXPOSE 3000 diff --git a/apps/web/src/app/(dashboard)/settings/api-keys/_components/api-keys-settings.tsx b/apps/web/src/app/(dashboard)/settings/api-keys/_components/api-keys-settings.tsx index fe194e2..794546b 100644 --- a/apps/web/src/app/(dashboard)/settings/api-keys/_components/api-keys-settings.tsx +++ b/apps/web/src/app/(dashboard)/settings/api-keys/_components/api-keys-settings.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useQuery, useMutation as useConvexMutation } from "convex/react"; import { useMutation } from "@tanstack/react-query"; import { api } from "@clawe/backend"; @@ -11,7 +11,6 @@ import { Spinner } from "@clawe/ui/components/spinner"; import { Skeleton } from "@clawe/ui/components/skeleton"; import { CheckCircle2, Eye, EyeOff, Pencil } from "lucide-react"; import { toast } from "sonner"; -import { loadPlugins, hasPlugin } from "@clawe/plugins"; import { patchApiKeys } from "@/lib/squadhub/actions"; import { useApiClient } from "@/hooks/use-api-client"; import { config } from "@/lib/config"; @@ -182,13 +181,7 @@ export const ApiKeysSettings = () => { const [openaiKey, setOpenaiKey] = useState(""); const [anthropicValid, setAnthropicValid] = useState(null); const [openaiValid, setOpenaiValid] = useState(null); - const [isCloud, setIsCloud] = useState(config.isCloud); - - useEffect(() => { - loadPlugins().then(() => { - setIsCloud(hasPlugin()); - }); - }, []); + const isCloud = config.isCloud; const anthropicValidation = useMutation({ mutationFn: async (apiKey: string) => { diff --git a/apps/web/src/app/api/auth/[...nextauth]/route.ts b/apps/web/src/app/api/auth/[...nextauth]/route.ts index 982fb19..26a1b86 100644 --- a/apps/web/src/app/api/auth/[...nextauth]/route.ts +++ b/apps/web/src/app/api/auth/[...nextauth]/route.ts @@ -1,3 +1,21 @@ -import { handlers } from "@/lib/auth/nextauth-config"; +import { type NextRequest, NextResponse } from "next/server"; +import { config } from "@/lib/config"; -export const { GET, POST } = handlers; +const notAvailable = () => + NextResponse.json({ error: "Not available" }, { status: 404 }); + +const isCognito = config.authProvider === "cognito"; + +export const GET = isCognito + ? notAvailable + : async (request: NextRequest) => { + const { handlers } = await import("@/lib/auth/nextauth-config"); + return handlers.GET(request); + }; + +export const POST = isCognito + ? notAvailable + : async (request: NextRequest) => { + const { handlers } = await import("@/lib/auth/nextauth-config"); + return handlers.POST(request); + }; diff --git a/apps/web/src/app/api/squadhub/health/route.ts b/apps/web/src/app/api/squadhub/health/route.ts index 943ebca..574a1cd 100644 --- a/apps/web/src/app/api/squadhub/health/route.ts +++ b/apps/web/src/app/api/squadhub/health/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { checkHealth } from "@clawe/shared/squadhub"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { getAuthenticatedTenant } from "@/lib/api/tenant-auth"; import { getConnection } from "@/lib/squadhub/connection"; import { config } from "@/lib/config"; @@ -19,8 +19,7 @@ async function isInfraRunning( ): Promise { try { if (config.isCloud) { - await loadPlugins(); - const lifecycle = getPlugin("squadhub-lifecycle"); + const lifecycle = await resolvePlugin("squadhub-lifecycle"); const status = await lifecycle.getStatus(tenantId); return status.running; } diff --git a/apps/web/src/app/api/tenant/provision/route.ts b/apps/web/src/app/api/tenant/provision/route.ts index 431c566..f8dbbb1 100644 --- a/apps/web/src/app/api/tenant/provision/route.ts +++ b/apps/web/src/app/api/tenant/provision/route.ts @@ -2,9 +2,12 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { ConvexHttpClient } from "convex/browser"; import { api } from "@clawe/backend"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { setupTenant } from "@/lib/squadhub/setup"; import { patchApiKeys } from "@/lib/squadhub/actions"; +import { logger as baseLogger } from "@/lib/logger"; + +const logger = baseLogger.child({ route: "tenant/provision" }); /** * POST /api/tenant/provision @@ -51,25 +54,36 @@ export const POST = async (request: NextRequest) => { try { // 2. Ensure account exists + logger.info("Ensuring account exists"); const account = await convex.mutation(api.accounts.getOrCreateForUser, {}); + logger.info({ accountId: account._id }, "Account ready"); // 3. Check for existing tenant const existingTenant = await convex.query( api.tenants.getForCurrentUser, {}, ); + logger.info( + { + hasTenant: !!existingTenant, + status: existingTenant?.status, + tenantId: existingTenant?._id, + }, + "Existing tenant check", + ); if (existingTenant && existingTenant.status === "active") { // Tenant already provisioned — just re-run app setup below + logger.info("Tenant already active, skipping provisioning"); } else { // 4. Create tenant + provision via plugin - await loadPlugins(); - const provisioner = getPlugin("squadhub-provisioner"); + const provisioner = await resolvePlugin("squadhub-provisioner"); // Create tenant record (or use existing non-active one) const tenantIdToProvision = existingTenant ? existingTenant._id : await convex.mutation(api.tenants.create, {}); + logger.info({ tenantId: tenantIdToProvision }, "Provisioning tenant"); // Provision infrastructure (dev: reads env vars) const provisionResult = await provisioner.provision({ @@ -77,6 +91,14 @@ export const POST = async (request: NextRequest) => { accountId: account._id, convexUrl, }); + logger.info( + { + squadhubUrl: provisionResult.squadhubUrl, + hasToken: !!provisionResult.squadhubToken, + metadata: provisionResult.metadata, + }, + "Provision result", + ); // Update tenant with connection details await convex.mutation(api.tenants.updateStatus, { @@ -90,22 +112,41 @@ export const POST = async (request: NextRequest) => { efsAccessPointId: provisionResult.metadata.efsAccessPointId, }), }); + logger.info("Tenant status updated to active"); } // Re-fetch tenant to get latest connection details const tenant = await convex.query(api.tenants.getForCurrentUser, {}); + logger.info( + { + tenantId: tenant?._id, + status: tenant?.status, + hasSquadhubUrl: !!tenant?.squadhubUrl, + hasSquadhubToken: !!tenant?.squadhubToken, + }, + "Re-fetched tenant", + ); if (!tenant) { + logger.error("Tenant not found after provisioning"); return NextResponse.json( { error: "Failed to retrieve tenant after provisioning" }, { status: 500 }, ); } else if (tenant.status !== "active") { + logger.error({ status: tenant.status }, "Tenant in unexpected status"); return NextResponse.json( { error: `Tenant in unexpected status "${tenant.status}"` }, { status: 500 }, ); } else if (!tenant.squadhubUrl || !tenant.squadhubToken) { + logger.error( + { + squadhubUrl: tenant.squadhubUrl ?? null, + hasToken: !!tenant.squadhubToken, + }, + "Tenant missing Squadhub connection details", + ); return NextResponse.json( { error: "Tenant missing Squadhub connection details" }, { status: 500 }, @@ -124,10 +165,21 @@ export const POST = async (request: NextRequest) => { tenant.openaiApiKey ?? undefined, connection, ); + logger.info("API keys patched"); } // 6. Run app-level setup (agents, crons, routines) + logger.info("Running app-level setup"); const result = await setupTenant(connection, convexUrl, authToken); + logger.info( + { + agents: result.agents, + crons: result.crons, + routines: result.routines, + errors: result.errors, + }, + "Setup complete", + ); // 7. Return result return NextResponse.json({ @@ -140,6 +192,7 @@ export const POST = async (request: NextRequest) => { }); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; + logger.error({ err: error }, "Provision failed"); return NextResponse.json({ error: message }, { status: 500 }); } }; diff --git a/apps/web/src/app/api/tenant/squadhub/restart/route.ts b/apps/web/src/app/api/tenant/squadhub/restart/route.ts index 926c2f4..c67ec3e 100644 --- a/apps/web/src/app/api/tenant/squadhub/restart/route.ts +++ b/apps/web/src/app/api/tenant/squadhub/restart/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { getAuthenticatedTenant } from "@/lib/api/tenant-auth"; /** @@ -14,8 +14,7 @@ export const POST = async (request: NextRequest) => { if (error) return error; try { - await loadPlugins(); - const lifecycle = getPlugin("squadhub-lifecycle"); + const lifecycle = await resolvePlugin("squadhub-lifecycle"); await lifecycle.restart(tenant._id); return NextResponse.json({ ok: true }); diff --git a/apps/web/src/app/api/tenant/squadhub/route.ts b/apps/web/src/app/api/tenant/squadhub/route.ts index 6e9789b..1edfa70 100644 --- a/apps/web/src/app/api/tenant/squadhub/route.ts +++ b/apps/web/src/app/api/tenant/squadhub/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { getAuthenticatedTenant } from "@/lib/api/tenant-auth"; /** @@ -14,8 +14,7 @@ export const DELETE = async (request: NextRequest) => { if (error) return error; try { - await loadPlugins(); - const lifecycle = getPlugin("squadhub-lifecycle"); + const lifecycle = await resolvePlugin("squadhub-lifecycle"); await lifecycle.destroy(tenant._id); return NextResponse.json({ ok: true }); diff --git a/apps/web/src/app/api/tenant/squadhub/status/route.ts b/apps/web/src/app/api/tenant/squadhub/status/route.ts index fca4887..0879d76 100644 --- a/apps/web/src/app/api/tenant/squadhub/status/route.ts +++ b/apps/web/src/app/api/tenant/squadhub/status/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { getAuthenticatedTenant } from "@/lib/api/tenant-auth"; /** @@ -15,8 +15,7 @@ export const GET = async (request: NextRequest) => { if (error) return error; try { - await loadPlugins(); - const lifecycle = getPlugin("squadhub-lifecycle"); + const lifecycle = await resolvePlugin("squadhub-lifecycle"); const status = await lifecycle.getStatus(tenant._id); return NextResponse.json(status); diff --git a/apps/web/src/app/api/tenant/status/route.ts b/apps/web/src/app/api/tenant/status/route.ts index 7c3f1fd..26a82a7 100644 --- a/apps/web/src/app/api/tenant/status/route.ts +++ b/apps/web/src/app/api/tenant/status/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { loadPlugins, getPlugin } from "@clawe/plugins"; +import { resolvePlugin } from "@/lib/plugins"; import { getAuthenticatedTenant } from "@/lib/api/tenant-auth"; /** @@ -15,8 +15,7 @@ export const GET = async (request: NextRequest) => { if (error) return error; try { - await loadPlugins(); - const provisioner = getPlugin("squadhub-provisioner"); + const provisioner = await resolvePlugin("squadhub-provisioner"); const status = await provisioner.getProvisioningStatus(tenant._id); return NextResponse.json(status); diff --git a/apps/web/src/lib/logger.ts b/apps/web/src/lib/logger.ts new file mode 100644 index 0000000..a6b3689 --- /dev/null +++ b/apps/web/src/lib/logger.ts @@ -0,0 +1,19 @@ +import pino from "pino"; + +const isDev = process.env.NODE_ENV !== "production"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || "info", + name: "web", + formatters: { + level: (label) => ({ level: label }), + }, + ...(isDev && { + transport: { + target: "pino-pretty", + options: { + colorize: true, + }, + }, + }), +}); diff --git a/apps/web/src/lib/plugins.ts b/apps/web/src/lib/plugins.ts new file mode 100644 index 0000000..277b75a --- /dev/null +++ b/apps/web/src/lib/plugins.ts @@ -0,0 +1,26 @@ +import { getPlugin, hasPlugin, registerPlugins } from "@clawe/plugins"; +import type { PluginMap } from "@clawe/plugins"; +import { config } from "@/lib/config"; +import { logger } from "@/lib/logger"; + +let loading: Promise | undefined; + +async function ensurePlugins(): Promise { + if (hasPlugin() || !config.isCloud) return; + if (!loading) { + loading = (async () => { + const { register } = await import( + /* webpackIgnore: true */ "@clawe/cloud-plugins" + ); + registerPlugins(register(logger), logger); + })(); + } + await loading; +} + +export async function resolvePlugin( + name: K, +): Promise { + await ensurePlugins(); + return getPlugin(name); +} diff --git a/packages/plugins/src/cloud-plugins.d.ts b/packages/plugins/src/cloud-plugins.d.ts index a8ff0b5..fcb7a0f 100644 --- a/packages/plugins/src/cloud-plugins.d.ts +++ b/packages/plugins/src/cloud-plugins.d.ts @@ -1,9 +1,10 @@ /** - * Type declaration for the optional external plugin package. - * Dynamically imported by loadPlugins(). If not installed, dev defaults are used. + * Type declaration for the optional cloud plugin package. + * Loaded at runtime when NEXT_PUBLIC_CLAWE_EDITION=cloud. */ declare module "@clawe/cloud-plugins" { import type { PluginMap } from "./registry"; + import type { PluginLogger } from "./interfaces/logger"; - export function register(): PluginMap; + export function register(logger?: PluginLogger): PluginMap; } diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index 7fa50eb..85c388c 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -1,9 +1,12 @@ +import "./cloud-plugins"; + // Registry -export { loadPlugins, hasPlugin, getPlugin } from "./registry"; +export { registerPlugins, hasPlugin, getPlugin } from "./registry"; export type { PluginMap } from "./registry"; // Interfaces export type { + PluginLogger, SquadhubProvisioner, ProvisionParams, ProvisionResult, diff --git a/packages/plugins/src/interfaces/index.ts b/packages/plugins/src/interfaces/index.ts index e8861b2..f849160 100644 --- a/packages/plugins/src/interfaces/index.ts +++ b/packages/plugins/src/interfaces/index.ts @@ -1,3 +1,5 @@ +export type { PluginLogger } from "./logger"; + export type { SquadhubProvisioner, ProvisionParams, diff --git a/packages/plugins/src/interfaces/logger.ts b/packages/plugins/src/interfaces/logger.ts new file mode 100644 index 0000000..caefd17 --- /dev/null +++ b/packages/plugins/src/interfaces/logger.ts @@ -0,0 +1,9 @@ +export interface PluginLogger { + debug(msg: string): void; + debug(obj: Record, msg: string): void; + info(msg: string): void; + info(obj: Record, msg: string): void; + error(msg: string): void; + error(obj: Record, msg: string): void; + child(bindings: Record): PluginLogger; +} diff --git a/packages/plugins/src/registry.spec.ts b/packages/plugins/src/registry.spec.ts index 82ea1bb..acdb664 100644 --- a/packages/plugins/src/registry.spec.ts +++ b/packages/plugins/src/registry.spec.ts @@ -1,15 +1,20 @@ import { describe, it, expect, beforeEach } from "vitest"; -import { loadPlugins, hasPlugin, getPlugin } from "./registry"; +import { registerPlugins, hasPlugin, getPlugin } from "./registry"; +import type { PluginMap } from "./registry"; import type { SquadhubProvisioner } from "./interfaces/squadhub-provisioner"; import type { SquadhubLifecycle } from "./interfaces/squadhub-lifecycle"; import { DefaultSquadhubProvisioner } from "./defaults/squadhub-provisioner"; import { DefaultSquadhubLifecycle } from "./defaults/squadhub-lifecycle"; describe("registry", () => { - describe("loadPlugins", () => { - it("falls back to dev defaults when cloud-plugins is not installed", async () => { - await loadPlugins(); - expect(hasPlugin()).toBe(false); + describe("registerPlugins", () => { + it("marks plugins as registered after calling registerPlugins", () => { + const map: PluginMap = { + "squadhub-provisioner": new DefaultSquadhubProvisioner(), + "squadhub-lifecycle": new DefaultSquadhubLifecycle(), + }; + registerPlugins(map); + expect(hasPlugin()).toBe(true); }); }); diff --git a/packages/plugins/src/registry.ts b/packages/plugins/src/registry.ts index 0eb45d8..8e3e25d 100644 --- a/packages/plugins/src/registry.ts +++ b/packages/plugins/src/registry.ts @@ -1,3 +1,4 @@ +import type { PluginLogger } from "./interfaces/logger"; import type { SquadhubProvisioner } from "./interfaces/squadhub-provisioner"; import type { SquadhubLifecycle } from "./interfaces/squadhub-lifecycle"; import { DefaultSquadhubProvisioner } from "./defaults/squadhub-provisioner"; @@ -13,33 +14,24 @@ let plugins: PluginMap = { "squadhub-lifecycle": new DefaultSquadhubLifecycle(), }; -let pluginsLoaded = false; +let registered = false; /** - * Initialize plugins. Call once at app startup. - * Attempts to load an external plugin package. - * If not available, keeps the dev defaults. + * Register plugin implementations. Call once at app startup. + * If never called, dev defaults are used. */ -export async function loadPlugins(): Promise { - if (pluginsLoaded) return; - - try { - const external = await import( - /* webpackIgnore: true */ "@clawe/cloud-plugins" - ); - plugins = external.register(); - pluginsLoaded = true; - } catch { - // No external plugins installed — using dev defaults. - } +export function registerPlugins(map: PluginMap, logger?: PluginLogger): void { + plugins = map; + registered = true; + logger?.info({ plugins: Object.keys(map) }, "Cloud plugins registered"); } -/** Returns true if external plugins are loaded (vs dev defaults). */ +/** Returns true if external plugins were registered (vs dev defaults). */ export function hasPlugin(): boolean { - return pluginsLoaded; + return registered; } -/** Get a plugin implementation. Always returns something (external or dev default). */ +/** Get a plugin implementation. Always returns something (registered or dev default). */ export function getPlugin(name: K): PluginMap[K] { return plugins[name]; } diff --git a/turbo.json b/turbo.json index 5241b10..4667f43 100644 --- a/turbo.json +++ b/turbo.json @@ -20,7 +20,23 @@ "COGNITO_DOMAIN", "WATCHER_TOKEN", "LOG_LEVEL", - "NEXT_RUNTIME" + "NEXT_RUNTIME", + "AWS_REGION", + "AWS_PROFILE", + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN", + "SQUADHUBS_CLUSTER_ARN", + "SQUADHUB_IMAGE", + "SQUADHUB_EXECUTION_ROLE_ARN", + "SQUADHUB_TASK_ROLE_ARN", + "SQUADHUB_LOG_GROUP", + "SQUADHUB_SECURITY_GROUP_ID", + "EFS_SECURITY_GROUP_ID", + "VPC_SUBNET_IDS", + "EFS_FILE_SYSTEM_ID", + "CLOUDMAP_NAMESPACE_ID", + "CLOUDMAP_NAMESPACE_NAME" ], "tasks": { "build": { From 00882b4acc8f0586b96b19bdaee52a9b0dab3128 Mon Sep 17 00:00:00 2001 From: Guy Ben Aharon Date: Mon, 23 Feb 2026 13:22:19 +0200 Subject: [PATCH 2/2] fix --- apps/web/src/lib/plugins.ts | 10 ++++++---- packages/plugins/src/cloud-plugins.d.ts | 10 ---------- packages/plugins/src/index.ts | 4 +--- packages/plugins/src/registry.ts | 3 +++ 4 files changed, 10 insertions(+), 17 deletions(-) delete mode 100644 packages/plugins/src/cloud-plugins.d.ts diff --git a/apps/web/src/lib/plugins.ts b/apps/web/src/lib/plugins.ts index 277b75a..192d0a7 100644 --- a/apps/web/src/lib/plugins.ts +++ b/apps/web/src/lib/plugins.ts @@ -1,18 +1,20 @@ import { getPlugin, hasPlugin, registerPlugins } from "@clawe/plugins"; -import type { PluginMap } from "@clawe/plugins"; +import type { PluginMap, CloudPluginRegister } from "@clawe/plugins"; import { config } from "@/lib/config"; import { logger } from "@/lib/logger"; +const CLOUD_PLUGINS_PKG = "@clawe/cloud-plugins"; + let loading: Promise | undefined; async function ensurePlugins(): Promise { if (hasPlugin() || !config.isCloud) return; if (!loading) { loading = (async () => { - const { register } = await import( - /* webpackIgnore: true */ "@clawe/cloud-plugins" + const mod: { register: CloudPluginRegister } = await import( + /* webpackIgnore: true */ CLOUD_PLUGINS_PKG ); - registerPlugins(register(logger), logger); + registerPlugins(mod.register(logger), logger); })(); } await loading; diff --git a/packages/plugins/src/cloud-plugins.d.ts b/packages/plugins/src/cloud-plugins.d.ts deleted file mode 100644 index fcb7a0f..0000000 --- a/packages/plugins/src/cloud-plugins.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Type declaration for the optional cloud plugin package. - * Loaded at runtime when NEXT_PUBLIC_CLAWE_EDITION=cloud. - */ -declare module "@clawe/cloud-plugins" { - import type { PluginMap } from "./registry"; - import type { PluginLogger } from "./interfaces/logger"; - - export function register(logger?: PluginLogger): PluginMap; -} diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index 85c388c..8efbb65 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -1,8 +1,6 @@ -import "./cloud-plugins"; - // Registry export { registerPlugins, hasPlugin, getPlugin } from "./registry"; -export type { PluginMap } from "./registry"; +export type { PluginMap, CloudPluginRegister } from "./registry"; // Interfaces export type { diff --git a/packages/plugins/src/registry.ts b/packages/plugins/src/registry.ts index 8e3e25d..4731a03 100644 --- a/packages/plugins/src/registry.ts +++ b/packages/plugins/src/registry.ts @@ -35,3 +35,6 @@ export function hasPlugin(): boolean { export function getPlugin(name: K): PluginMap[K] { return plugins[name]; } + +/** Function signature that cloud-plugins must export as `register`. */ +export type CloudPluginRegister = (logger?: PluginLogger) => PluginMap;