Skip to content

fix(cloudflare): make sandboxed plugins a paid-plan opt-in - #2351

Merged
ascorbic merged 8 commits into
mainfrom
feat/gate-dynamic-plugins-worker-loader
Sep 11, 2026
Merged

fix(cloudflare): make sandboxed plugins a paid-plan opt-in#2351
ascorbic merged 8 commits into
mainfrom
feat/gate-dynamic-plugins-worker-loader

Conversation

@MattieTK

@MattieTK MattieTK commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

New Cloudflare scaffolds leave the paid-plan Worker Loader binding disabled by default, so a generated site can deploy on the Workers free plan without editing wrangler.jsonc.

  • create-emdash asks whether to enable sandboxed plugins on Cloudflare, defaults to no, and supports --sandboxed-plugins / --no-sandboxed-plugins for non-interactive use. It normalizes both the current commented binding and the older multiline binding.
  • sandbox() from @emdash-cms/cloudflare reads the project’s Wrangler config at build time and selects the Cloudflare runner only when the LOADER binding is present. It honors the named environment selected through CLOUDFLARE_ENV, so Worker Loader remains the source of truth for default and named environments.
  • A configured marketplace or registry can remain available for browsing without a runner. Sandboxed config plugins do not load, and install or update requests return SANDBOX_NOT_AVAILABLE until a runner is enabled.
  • The install, configuration, and Plugin Sandbox guides document the free-plan default, the paid-plan opt-in, Node/workerd setup, Miniflare’s optional-dependency behavior, and non-downloading troubleshooting commands.

Existing Cloudflare projects keep their checked-in Wrangler configuration. Existing Node sites with the workerd runner keep the same runtime behavior. The implementation originally authored by @MattieTK remains in its original commit; current main was merged without rebasing or force-pushing.

Part of #1680.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main. (n/a: no admin UI changes)
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/... (n/a: this fixes the default scaffold deployment)
  • I have included screenshots below if this PR changes the UI (n/a: no UI changes)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8; OpenAI GPT-5 (Codex)

Screenshots / test output

Screenshots are not applicable because this PR does not change the UI.

Verified on the current head:

pnpm install --frozen-lockfile                         pass
pnpm build                                             pass
pnpm typecheck                                         pass
pnpm lint                                              pass
pnpm format:check                                      pass
pnpm typecheck:templates                               pass (9 projects)
pnpm --dir docs build                                  pass

@emdash-cms/cloudflare                                 38 passed, 1 skipped files; 423 passed, 2 skipped tests
Cloudflare sandbox config regression                   4 passed
Marketplace-without-runner regression                  1 passed
create-emdash flags and Wrangler normalization         111 passed

The blog-cloudflare scaffold also completed astro build, logging the expected build-time warning that sandboxed plugins are disabled. Its redirected Wrangler 4.124.0 dry run completed with KV, D1, R2, Images, and Assets bindings and no Worker Loader binding.

Copilot AI lite review requested due to automatic review settings August 6, 2026 17:35
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7418fcd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/cloudflare Patch
create-emdash Patch
emdash Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/release-service Patch
@emdash-cms/plugin-embeds Patch

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
emdash-demo-cache 6f46425 Aug 21 2026, 12:35 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 92e74ae Aug 16 2026, 10:42 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
emdash-demo-do 6f46425 Aug 21 2026, 12:35 PM

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Aug 6, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach judgment: This is the right change for the stated problem. Gating dynamic plugins behind real sandbox availability keeps the default Cloudflare scaffold deployable on the Workers free tier, gives users a clear opt-in path, and surfaces missing setup in the admin UI instead of a late 503. The maintainer-requested origin makes the lack of a separate Discussion reasonable. The design is additive and backwards-compatible: new scaffolds default to worker_loaders commented out; existing sites with the binding keep reporting sandboxAvailable: true and see no behavior change.

What I checked: full diff, the changed runtime/admin/create-emdash files, route structure around marketplace/registry, the manifest endpoint route, SandboxRunner usage, the existing globalThis singleton patterns in emdash-runtime.ts, and the changeset config (which groups emdash, @emdash-cms/admin, and create-emdash in one fixed release, so the two changesets cover the admin changes as well).

Headline conclusion: Implementation is solid and well-tested. I found one AGENTS.md convention issue with a new module-scope singleton cache and one UX suggestion to avoid rendering the marketplace UI while the manifest is still loading. Both are fixable in small follow-up edits; neither blocks understanding the PR.

Comment thread packages/core/src/emdash-runtime.ts Outdated
Comment thread packages/admin/src/router.tsx Outdated
Comment thread packages/admin/src/router.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 693 lines across 20 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond query-count changed PR diff modifies query-count snapshot files and removed review/needs-review No maintainer or bot review yet labels Aug 6, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2351

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2351

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2351

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2351

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2351

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2351

emdash

npm i https://pkg.pr.new/emdash@2351

create-emdash

npm i https://pkg.pr.new/create-emdash@2351

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2351

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2351

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2351

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2351

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2351

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@2351

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2351

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2351

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2351

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2351

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2351

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2351

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2351

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2351

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2351

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2351

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2351

commit: 7418fcd

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot removed the query-count changed PR diff modifies query-count snapshot files label Aug 7, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 8c31ec2 Sep 11 2026, 09:59 AM

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review cla: signed area/docs and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 7, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 7, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right change for the stated free-tier deploy blocker: gating dynamic plugins behind real sandbox availability keeps the default Cloudflare scaffold deployable, gives users a clear opt-in path, and surfaces missing setup in the admin UI instead of a late 503. The design is additive and backwards-compatible throughout.

In this re-review I checked the full diff, the changed runtime/admin/create-emdash files, the manifest endpoint, SandboxRunner usage, the existing globalThis singleton patterns, and the changeset config.

Headline conclusion: the implementation is solid and well-tested. The blocking-style concern from the previous review — the new module-scope availability cache not living on globalThis — has been fixed: getSandboxRunnerAvailabilityCache() now stores the WeakMap behind Symbol.for("emdash:sandbox-runner-availability") on globalThis, matching the DB_HOLDER_KEY / SEED_HOLDER_KEY patterns already in emdash-runtime.ts.

The previous UX suggestion about avoiding the marketplace UI while the manifest is still loading has been addressed with an explicit design choice: the routes wait for the manifest before deciding, and deliberately fall through to the browse/detail UI during load so the common paid-tier path doesn't flash the unavailable prompt. I'm not raising it again as a finding because that's a reasonable judgment call.

I found no new logic bugs, regressions, security issues, or AGENTS.md convention violations. Test coverage is good across create-emdash, emdash runtime, and admin routes. Changeset grouping covers @emdash-cms/admin via the fixed release. LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 7, 2026
@github-actions github-actions Bot added the review/approved Approved; no new commits since label Aug 7, 2026
@ascorbic ascorbic changed the title feat: gate sandboxed plugins behind Worker Loader so free-tier Cloudflare deploys work fix(cloudflare): make sandboxed plugins a paid-plan opt-in Sep 10, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 10, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Sep 10, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 10, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right change for the stated free-tier deploy blocker. Gating the Cloudflare sandbox runner on the real worker_loaders binding makes new Cloudflare scaffolds deployable on the Workers free plan, preserves paid-tier behavior when the binding is present, and routes install/update requests through the existing SANDBOX_NOT_AVAILABLE envelope instead of a late runtime failure. It is additive and backwards-compatible for existing Cloudflare and Node projects.

I reviewed the full diff, the changed runtime / admin / create-emdash files, the Cloudflare adapter, the SANDBOX_NOT_AVAILABLE paths in the marketplace and registry handlers, the sandbox runner virtual-module wiring, the four Cloudflare templates, and the changeset. I also checked the updated docs against the implementation (the writing-emdash-docs skill was not available via the tool, so I verified the prose manually).

Headline conclusion: the implementation is solid and well-tested. No new logic bugs, regressions, security issues, or AGENTS.md convention violations were introduced in this revision. I am not re-raising the two prior-review concerns that were already addressed (the globalThis availability-cache pattern and the deliberate marketplace-loading UX choice), because the current diff shows them resolved or unchanged.

I have two very minor test-maintenance suggestions, both non-blocking.


Findings

  • [suggestion] packages/create-emdash/tests/flags.test.ts:373-381

    The HELP_TEXT sync-check list was not updated for the new flags. The test still passes because the old flags are present, but it no longer actually checks that every supported flag is documented.

    		for (const flag of [
    			"--template",
    			"--platform",
    			"--pm",
    			"--package-manager",
    			"--install",
    			"--no-install",
    			"--sandboxed-plugins",
    			"--no-sandboxed-plugins",
    			"--yes",
    			"--force",
    			"--help",
    		]) {
    
  • [suggestion] packages/core/tests/unit/astro/integration/marketplace-sandbox.test.ts:6

    The inner test description says "themes" but the test configures the plugin marketplace. This appears to be a copy/paste leftover.

    	it("allows the marketplace to remain available when sandboxed plugins are disabled", () => {
    

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 10, 2026
@ascorbic
ascorbic self-requested a review September 10, 2026 15:38

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right change for the free-tier deploy blocker. Gating the Cloudflare sandbox runner on the actual worker_loaders binding keeps new Cloudflare scaffolds deployable on the Workers free plan, preserves paid-tier behavior when the binding is present, and routes install/update requests through the existing SANDBOX_NOT_AVAILABLE envelope instead of a late runtime failure. It is additive and backwards-compatible for existing Cloudflare and Node projects.

Re-review status: Both prior emdashbot[bot] suggestions were addressed in this revision — the HELP_TEXT sync-check list now covers --sandboxed-plugins / --no-sandboxed-plugins, and the marketplace-without-runner test description no longer says "themes".

I reviewed the full diff, the changed runtime / create-emdash files, the Cloudflare adapter, the SANDBOX_NOT_AVAILABLE paths in packages/core/src/api/handlers/marketplace.ts and registry.ts, the sandbox() implementation and tests, the four Cloudflare templates, and the updated docs. I verified that the templates export PluginBridge, that the handlers return SANDBOX_NOT_AVAILABLE when the runner is missing or unavailable, and that sandbox() honors CLOUDFLARE_ENV and the exact LOADER binding name.

I found one remaining issue: the docs incorrectly state that Wrangler named environments do not inherit top-level bindings. That will lead users to repeat the LOADER binding in every environment unnecessarily. The fix is a wording adjustment; the implementation itself is solid.

Everything else is clean: no new logic bugs, security issues, AGENTS.md convention violations, or logged-out hot-path query regressions. The changeset is adequate and the tests cover the new behavior.

Comment thread docs/src/content/docs/deployment/plugin-sandbox.mdx
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 10, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator

I checked the named-environment suggestion against Wrangler 4.124.0 rather than changing the guide: unstable_readConfig({ env: "production" }) returns an empty worker_loaders array when LOADER exists only at the top level. Wrangler’s current type contract also marks worker_loaders as @nonInheritable, and the Cloudflare configuration docs say bindings must be declared in each named environment.

The existing wording is therefore accurate, so I have left it unchanged.

~ 🤖 Codex

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Sep 10, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 10, 2026

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-gate-dynamic-plugins-worker-loader.try.emdashcms.com, https://feat-gate-dynamic-plugins-worker-loader-emdash-playground.emdash-cms.workers.dev (commit 7418fcd)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://83af526f.try.emdashcms.com, https://83af526f-emdash-playground.emdash-cms.workers.dev 7418fcd 2026-09-11T11:18:12.580Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://58abda44.try.emdashcms.com, https://58abda44-emdash-playground.emdash-cms.workers.dev 8c31ec2 2026-09-11T09:59:59.963Z Visit the dashboard ↗
  • Build: In progress 🔵

View logs ↗
79eacce 2026-09-11T09:51:59.238Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://d402f03d.try.emdashcms.com, https://d402f03d-emdash-playground.emdash-cms.workers.dev 046e9e0 2026-09-10T15:42:03.811Z Visit the dashboard ↗

…gins-worker-loader

# Conflicts:
#	docs/src/content/docs/plugins/installing.mdx
#	docs/src/content/docs/reference/configuration.mdx
…gins-worker-loader

# Conflicts:
#	docs/src/content/docs/deployment/plugin-sandbox.mdx
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Sep 11, 2026
@ascorbic
ascorbic merged commit f0af9a1 into main Sep 11, 2026
52 checks passed
@ascorbic
ascorbic deleted the feat/gate-dynamic-plugins-worker-loader branch September 11, 2026 13:07
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Sep 11, 2026
@emdashbot emdashbot Bot mentioned this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants