feat(plugins): unify 3 extension points into apiDispatch#1
Open
lauer3912 wants to merge 5 commits into
Open
Conversation
added 5 commits
July 13, 2026 17:39
…r dep Move ServerRuntime and RouteHandler from server/router.ts into a new server/routerTypes.ts module so the plugin public-routes registry can type its handler without depending on the dispatcher: router.ts -> apiDispatch.ts -> extensions/publicRoutesProtocol.ts -> extensions/publicRoutes.ts -> router.ts That cycle only closes now that RouteHandler is exported (it was declared local, so publicRoutes.ts's import previously failed). Hoisting both types to a neutral file breaks the cycle at the source. Also lock the three new plugin extension-point targets (cms.migrations.register, cms.publicRoutes.register, cms.httpMiddleware.register) into the architecture-test contracts so the schema / handler / permission table can never silently desync. Tests: - src/__tests__/architecture/plugin-rpc-target-registry.test.ts - src/__tests__/architecture/plugin-sandbox-invariants.test.ts Verification: bun test src/__tests__/architecture/no-circular-dependencies.test.ts + plugin-rpc-target-registry.test.ts + plugin-sandbox-invariants.test.ts — all pass.
…errors
Wire the three plugin extension points (migrations registration,
public-route registration, HTTP middleware registration) into the
single apiDispatch handler table so plugin code reaches them through
the same permission-gated path as every other api-call — no
special-case host dispatchers, no ad-hoc host routes.
What changed:
- server/plugins/host/apiDispatch.ts: register the three new
handlers in the typed handler table (compile-enforced by
`satisfies HostApiHandlerTable`).
- server/plugins/extensions/{migrations,publicRoutes,httpMiddleware}Protocol.ts:
add the missing `_entry: HostPluginRecord` parameter to match
the three-arg HostApiHandler signature (msg, entry, db), and fix
the import paths the three handlers had wrong
(`../../protocol/...` was one level too deep; the public-route
handler's unused `runtime` arg is now `_runtime`).
- server/publish/publicRouter.ts: `renderNotFoundResponse` gains
a `req?: Request` parameter so the Layer-B render can pass
viewer context to the not-found template (router.ts already
forwards the request). Also restore the trailing `}` the
previous commit stripped, drop the unused `resolveViewerContext`
import, and drop the now-unused `req?` from
`renderPublicResolution` (its body never read it).
- server/publish/publicRenderer.ts: import `resolveViewerContext`
(used inside `buildViewerFrame`).
- server/router.ts: drop the two now-unused plugin-route imports
left behind when the dedicated `tryServePluginPublicRoute`
handler was retired.
- server/plugins/adminUi/apiKeysPage.ts: `db` -> `_db` (parameter
was never read).
- src/core/templates/tokenInterpolation.ts: delete the
unreachable `case 'viewer':` — `DynamicPropBinding['source']`
no longer includes 'viewer', so the case was dead.
- server/plugins/protocol/apiCallSchema.ts: import the 3 new arg
schemas (MigrationsRegisterArgSchema / PublicRoutesRegisterArgSchema
/ HttpMiddlewareRegisterArgSchema) that a previous commit
registered but never imported — the typecheck has been relying
on these imports living uncommitted in the working tree.
Verification:
- bunx tsc -p tsconfig.node.json --noEmit 0 errors
- bunx tsc -p tsconfig.app.json --noEmit 0 errors
- bunx tsc -p tests/e2e --noEmit 0 errors
- bun run typecheck:plugins 0 errors across 8 plugins
- bun run build:plugins 8/8 OK
- bun run build (tsc -b && vite build) OK
Re-run `bun run bootstrap:sync` after `apiCallSchema.ts` picked up the three new `cms.migrations.register` / `cms.publicRoutes.register` / `cms.httpMiddleware.register` schemas, so the VM-side `assertPermission` table and the worker-protocol allowlist match the host dispatcher again. Without this, the architecture gates `src/__tests__/architecture/plugin-bootstrap-fresh.test.ts` and `plugin-sandbox-invariants.test.ts` would fail on every CI run.
…ate round-trip `scripts/e2e-plugin-install.ts` is an end-to-end smoke test for the plugin install path. It boots the host (CMS only, no vite) on a free port with a temporary SQLite database, creates the owner, runs the step-up window, installs the requested plugin from a built bundle as a .zip, activates it, and calls one of its endpoints to verify the round trip works. Usage: bun run scripts/e2e-plugin-install.ts bun run scripts/e2e-plugin-install.ts instatic.api-keys bun run scripts/e2e-plugin-install.ts instatic.public-auth The script uses `fflate` for the zip writer, `spawn` to start the host in a child process, and a small typed `Client` class to drive the HTTP surface. Exit 0 on success, non-zero on failure. `plugins/api-keys/package.json` and `plugins/public-auth/package.json` are touched in lockstep so these two plugins' `instaticManifest` metadata stays compatible with the e2e harness (network allowed hosts, permissions, etc.).
…very codes
`generateRecoveryCodes` previously encoded 8 random bytes as
base64url (11 chars), uppercased, stripped the `-` / `_`
characters with `.replace(/[^A-Z0-9]/g, '')`, and `.slice(0, 10)`'d
the result. The strip step silently produced 8-10 char codes
depending on how many of the 11 base64url chars landed in the
index-62/63 range — the test asserted `/^[A-Z0-9]{10}$/` and would
fail on a run where three `-_` chars had been removed.
Generate exactly 10 alphanumeric chars instead: 10 random bytes
mapped directly to indices in a 36-char alphabet. Modulo bias
(256 % 36 = 4) is acceptable — recovery codes are rate-limited and
shown to the user once, not a bearer of long-lived access.
Discovered by `bun test plugins/` while verifying the plugin
extension-points refactor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wire the three plugin extension points (migrations, publicRoutes,
httpMiddleware) into the single
apiDispatchhandler table so plugincode reaches them through the same permission-gated path as every
other api-call. Closes a 22-error TS drift that the working tree had
been carrying uncommitted, and adds a script for end-to-end
plugin-install verification.
Commits (6)
5385f818feat: CI pipeline + fix admin UI endpoint paths + complete tier CRUD UI50a3e5b2refactor(router): hoist ServerRuntime + RouteHandler to break circular depaa94c8adfeat(plugins): unify 3 extension points into apiDispatch + fix 22 TS errorsd158fb98chore(plugins): regenerate QuickJS bootstrap for 3 new schema targetsf61ba262test(plugins): add e2e-plugin-install script for plugin install/activate round-trip07df63d5fix(public-auth): generate exactly 10 alphanumeric chars in TOTP recovery codesWhy this change
The plugin system has three "extension points" — places where a
plugin's worker can register something with the host:
receivers, etc.)
Until this PR, plugin code reached the host through a mix of paths:
two of the three went through the standard
apiDispatchhandlertable, but the third (public routes) had its own dedicated
host-side dispatcher (
tryServePluginPublicRoute) that bypassed thecentral permission gate. This refactor pulls all three into the
same code path so they share one permission map, one audit log, one
failure mode.
The refactor surfaced 22 pre-existing TS errors that had been
silently fixed in the working tree (most notably the
apiCallSchema.tsimports for the three new arg schemas) — thisPR also cleans them up.
User / developer impact
still register via the same SDK surface; the change is entirely
host-internal.
Verification
Run on the
feat/plugin-extensions-unifybranch:bunx tsc -p tsconfig.node.json --noEmitbunx tsc -p tsconfig.app.json --noEmitbunx tsc -p tests/e2e --noEmitbun run typecheck:pluginsbun run build:pluginsbun run build(tsc -b + vite build)bun test plugins/bun test src/__tests__/architecture/no-circular-dependenciesbun test src/__tests__/architecture/plugin-rpc-target-registrybun test src/__tests__/architecture/plugin-sandbox-invariantsbun test src/__tests__/architecture/plugin-bootstrap-freshbun test src/__tests__/architecture/migration-paritybunx eslint scripts/e2e-plugin-install.tsKnown issues (pre-existing, out of scope)
src/__tests__/architecture/(BTN-3 button primitive, CodeMirrorlazy-load, dispatcher HTML pipeline, Error boundary coverage,
Keybindings registry) — introduced by commits before this branch.
bun run linterrors underplugins/*/src/(unused vars,
anycasts) — same source.bun test src/(the src/-side unit tests) was not run end-to-endon Windows in this session — the 5-minute tool timeout is too
short for the 262-test suite. CI on Linux runs the full suite as
job 2
unit-tests.