fix(core): trusted standard-format route handlers get the public single-arg RouteContext#2136
fix(core): trusted standard-format route handlers get the public single-arg RouteContext#2136dchaudhari7177 wants to merge 6 commits into
Conversation
…-arg RouteContext adaptSandboxEntry always invoked route handlers with the sandboxed two-arg (routeCtx, pluginCtx) convention. A standard-format plugin authored against the exported single-arg RouteContext API silently got routeCtx as its only argument (JS drops the extra parameter), so ctx.storage / ctx.email / ctx.kv and every other capability surface were undefined and any route touching them threw at runtime. The definition's id is the format signal — definePlugin requires it and sandbox-format default exports never carry one (identity comes from the manifest), the same rule define-plugin.ts documents. Standard-format handlers now receive the incoming RouteContext unchanged; sandbox-format entries keep the two-arg flattened convention. Closes emdash-cms#2079
🦋 Changeset detectedLatest commit: a15b352 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
This is the right fix for the right problem. adaptSandboxEntry was written assuming every standard/trusted plugin default export used the sandboxed two-arg route convention, so definePlugin-authored plugins placed in plugins: [] silently received only the route-specific slice of RouteContext and blew up on ctx.storage, ctx.email, etc. The PR keys the dispatch off the same structural signal the codebase already uses (definePlugin requires id; sandbox default exports never carry one), which is consistent with define-plugin.ts and avoids changing the public API.
I checked:
- The diff and full changed files in
packages/core/src/plugins/adapt-sandbox-entry.tsand the unit tests. - Call-sites in
packages/core/src/emdash-runtime.tsandpackages/core/src/astro/integration/virtual-modules.ts— the adapter is used for bothplugins: []trusted installs and marketplace/debug-bypass trusted loads, so the same format-detection benefits both paths. - The plugin-cli build pipeline (
packages/plugin-cli/src/build/pipeline.ts) —ProbedDefaultSchemais alooseObjectover{ hooks, routes }, so adefinePlugin(...)shaped default export (which returns aResolvedPlugincontainingid) is not rejected at bundle time, and itsidsurvives into the runtime artifact handled byadaptSandboxEntry. packages/core/src/plugins/routes.ts— the runtime router always callsroute.handler(routeContext)with a singleRouteContext; the adapter’s public-format passthrough is exactly what the public contract promises.- AGENTS.md conventions: no SQL, no new content-table queries, no admin UI strings, no migrations, and a proper user-facing changeset under the published package name (
emdash).
The added tests cover the regression directly: a definePlugin-shaped definition receives the full RouteContext as its only argument (same object identity, real Request, capabilities reachable), while a sandbox-format definition still gets the flattened two-arg convention. Existing behavior for sandbox-format entries is preserved byte-for-byte.
No blocking issues found.
The id check cast `definition` to `{ id?: unknown }`, which trips
oxlint's no-unsafe-type-assertion under --deny-warnings and failed Lint.
Use the `in` operator instead — it narrows without an assertion and
reads the same.
What does this PR do?
adaptSandboxEntry(the in-process adapter behindplugins: []) always invoked route handlers with the sandboxed two-arg(routeCtx, pluginCtx)convention. A standard-format plugin authored against the public, exported, type-checked single-argRouteContextAPI silently receivedrouteCtxas its only argument (JS drops the extra parameter for a one-param function), soctx.storage,ctx.email,ctx.kvand every other capability surface wereundefined— any route touching more thaninput/request/requestMetathrewTypeError: Cannot read properties of undefined.The fix keys off the format signal the codebase already documents (
define-plugin.ts):definePluginrequiresid, and sandbox-format default exports never carry one (identity comes from the manifest's slug + publisher). When the definition has anid, the wrapper passes the incomingRouteContextthrough unchanged (fullPluginContext+input+ real WHATWGRequest+requestMeta, exactly what the public contract promises). Sandbox-format entries keep the existing two-arg flattened convention, byte for byte.This covers both trusted paths: config
plugins: [somePlugin()]and marketplace bundles built by plugin-cli (which bundle the plugin's owndefinePluginentry, so theidsurvives).Closes #2079
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output
New tests: a
definePlugin-shaped definition's handler receives the fullRouteContextas its only argument (same object identity, realRequest,storagereachable); a sandbox-format definition (noid) still gets the flattened two-arg convention. Full plugin unit suite: 622/622 pass (tests/unit/plugins, 30 files). Prettier clean.