diff --git a/.changeset/plugin-route-site-context.md b/.changeset/plugin-route-site-context.md new file mode 100644 index 0000000000..156f51eb1d --- /dev/null +++ b/.changeset/plugin-route-site-context.md @@ -0,0 +1,5 @@ +--- +"emdash": patch +--- + +Fixes trusted plugin API routes receiving empty site metadata and relative URLs by using the same site context as plugin hooks. diff --git a/packages/core/src/emdash-runtime.ts b/packages/core/src/emdash-runtime.ts index ecf6fa0c82..004d3f93df 100644 --- a/packages/core/src/emdash-runtime.ts +++ b/packages/core/src/emdash-runtime.ts @@ -3333,8 +3333,7 @@ export class EmDashRuntime { const trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId); if (trustedPlugin && this.enabledPlugins.has(trustedPlugin.id)) { const routeRegistry = new PluginRouteRegistry({ - db: this.db, - storage: this.storage ?? undefined, + ...this.pipelineFactoryOptions, emailPipeline: this.email ?? undefined, trustedProxyHeaders: getTrustedProxyHeaders(this.config), }); diff --git a/packages/core/tests/integration/runtime/plugin-route-site-info.test.ts b/packages/core/tests/integration/runtime/plugin-route-site-info.test.ts new file mode 100644 index 0000000000..112ffe9c0c --- /dev/null +++ b/packages/core/tests/integration/runtime/plugin-route-site-info.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; + +import type { EmDashConfig } from "../../../src/astro/integration/runtime.js"; +import { EmDashRuntime } from "../../../src/emdash-runtime.js"; +import type { RuntimeDependencies } from "../../../src/emdash-runtime.js"; +import { definePlugin } from "../../../src/plugins/define-plugin.js"; +import { createHookPipeline } from "../../../src/plugins/hooks.js"; + +function buildRuntime(): EmDashRuntime { + // This route only reads site context, so it never touches the database. + const db = {} as never; + const plugin = definePlugin({ + id: "site-aware-route", + version: "1.0.0", + routes: { + inspect: { + handler: async (ctx) => ({ + site: ctx.site, + url: ctx.url("/checkout/success"), + }), + }, + }, + }); + const config: EmDashConfig = {}; + const pipelineFactoryOptions = { + db, + siteInfo: { + siteName: "Example Site", + siteUrl: "https://example.com/", + locale: "nl", + }, + } as const; + const hooks = createHookPipeline([plugin], pipelineFactoryOptions); + const pipelineRef = { current: hooks }; + const runtimeDeps: RuntimeDependencies = { + config, + plugins: [plugin], + createDialect: () => { + throw new Error("createDialect not used in this test"); + }, + createStorage: null, + sandboxEnabled: false, + sandboxedPluginEntries: [], + createSandboxRunner: null, + }; + + return new EmDashRuntime({ + db, + storage: null, + configuredPlugins: [plugin], + sandboxedPlugins: new Map(), + sandboxedPluginEntries: [], + hooks, + enabledPlugins: new Set([plugin.id]), + pluginStates: new Map(), + config, + mediaProviders: new Map(), + mediaProviderEntries: [], + cronExecutor: null, + cronScheduler: null, + emailPipeline: null, + allPipelinePlugins: [plugin], + pipelineFactoryOptions, + runtimeDeps, + pipelineRef, + }); +} + +describe("EmDashRuntime.handlePluginApiRoute site context", () => { + it("passes configured site information to trusted plugin routes", async () => { + const runtime = buildRuntime(); + const result = await runtime.handlePluginApiRoute( + "site-aware-route", + "GET", + "/inspect", + new Request("https://admin.example.com/_emdash/api/plugin/site-aware-route/inspect"), + ); + + expect(result).toMatchObject({ + success: true, + data: { + site: { + name: "Example Site", + url: "https://example.com", + locale: "nl", + }, + url: "https://example.com/checkout/success", + }, + }); + }); +});