Skip to content

feat: add Cloudflare deployment adapter#192

Open
rturnq wants to merge 5 commits into
mainfrom
claude/cloudflare-adapter
Open

feat: add Cloudflare deployment adapter#192
rturnq wants to merge 5 commits into
mainfrom
claude/cloudflare-adapter

Conversation

@rturnq

@rturnq rturnq commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a new adapter package, @marko/run-adapter-cloudflare, for previewing and deploying Marko Run apps to Cloudflare. It supports both Cloudflare targets via a mode option:

  • mode: "workers" (default) — builds a bundled _worker.js (export default { fetch }) for Cloudflare Workers with a static assets binding, and generates a starter wrangler.json pointing at the worker and the public/ assets. An existing project-level Wrangler config (wrangler.toml/.json/.jsonc) is left untouched so user-defined bindings/secrets/name are preserved.
  • mode: "pages" — builds a Cloudflare Pages "advanced mode" _worker.js alongside the static assets and emits a _routes.json that excludes the static asset directory from invoking the function.

Implementation mirrors the existing Netlify edge adapter:

  • SSR target webworker with workerd/worker export conditions and noExternal: true (single self-contained bundle).
  • A single default-entry that calls @marko/run's router and falls back to env.ASSETS.fetch when no route matches.
  • Route handlers receive { env, ctx, cf } via the exported CloudflarePlatformInfo type.
  • Preview runs through the Wrangler CLI (wrangler dev for Workers, wrangler pages dev for Pages).

Also adds two test fixtures to the @marko/run package (cloudflare-adapter-workers and cloudflare-adapter-pages) mirroring the Netlify fixtures, and a changeset for the new package.

Motivation and Context

Cloudflare is one of the most-requested deployment targets and the canonical edge platform, but Marko Run shipped no adapter for it — users had to wire up Workers/Pages by hand. Because the runtime is built on web standards, a Cloudflare adapter is a natural fit and fills the largest gap in deployment coverage alongside the existing Node, static, and Netlify adapters.

Screenshots (if appropriate):

Checklist:

  • I have updated/added documentation affected by my changes.
  • I have added tests to cover my changes.

Generated by Claude Code

rturnq added 2 commits June 30, 2026 22:47
Add @marko/run-adapter-cloudflare for deploying Marko Run apps to
Cloudflare Workers (default) or Cloudflare Pages.

- Builds for the webworker SSR target with workerd export conditions
- Workers mode emits a bundled `_worker.js` plus a generated wrangler.json
  (skipped when a project-level Wrangler config exists)
- Pages mode emits an advanced-mode `_worker.js` and a `_routes.json`
  that excludes static assets from the function
- Exposes env/ctx/cf to route handlers via CloudflarePlatformInfo
- Previews via the Wrangler CLI (`wrangler dev` / `wrangler pages dev`)
Add dev test fixtures for both Cloudflare adapter modes, mirroring the
existing Netlify adapter fixtures. Preview is skipped because it requires
the Wrangler CLI (unavailable in CI), matching the Netlify edge fixture.

Verified the build output for each mode:
- workers: dist/_worker.js (default fetch export with ASSETS fallback) and
  a generated dist/wrangler.json pointing at the worker and public assets
- pages: dist/public/_worker.js plus dist/public/_routes.json excluding the
  static assets dir
@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 153dc22

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

This PR includes changesets to release 1 package
Name Type
@marko/run-adapter-cloudflare Minor

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

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20949809-6e4b-473e-a558-cd7b1b64ac15

📥 Commits

Reviewing files that changed from the base of the PR and between b520472 and 153dc22.

📒 Files selected for processing (1)
  • packages/adapters/cloudflare/README.md
✅ Files skipped from review due to trivial changes (1)
  • packages/adapters/cloudflare/README.md

Walkthrough

This PR adds a new @marko/run-adapter-cloudflare package for Cloudflare Workers and Cloudflare Pages. It includes the adapter runtime, Cloudflare-specific types, build scripts, package metadata, documentation, a changeset, a cspell update, and Workers/Pages test fixtures with route typings, example pages, and test configs.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a Cloudflare deployment adapter.
Description check ✅ Passed The description is directly related and accurately details the new Cloudflare adapter, modes, docs, and fixtures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cloudflare-adapter

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/adapters/cloudflare/scripts/build.ts (1)

40-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

splitting: true diverges from the sibling build script's convention.

The analogous esm build in packages/run/scripts/build.ts uses splitting: !true (i.e. false). Here it's set to true for the single-entry esm build (src/index.ts), which has no practical effect today but is an unexplained deviation, and could silently start emitting chunk files if dynamic imports are ever introduced into src/index.ts, changing the published dist layout.

♻️ Suggested fix to align with the established pattern
   build({
     ...opts,
     format: "esm",
-    splitting: true,
+    splitting: false,
   }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/cloudflare/scripts/build.ts` around lines 40 - 44, The
single-entry ESM build in build() is using splitting: true, which deviates from
the sibling script’s convention and could change the emitted dist layout if
dynamic imports appear later. Update the cloudflare build configuration to match
the existing pattern used in the analogous build script by disabling splitting
for the src/index.ts ESM output, keeping the build behavior aligned and stable.
packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider ignoring .wrangler/ local cache too.

wrangler dev/wrangler pages dev write a local .wrangler cache directory; worth adding alongside dist to avoid accidental commits during local preview testing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore` at
line 1, The fixture .gitignore currently ignores only dist, but wrangler dev and
wrangler pages dev also create a local .wrangler cache directory that should be
excluded. Update the .gitignore content in this fixture to ignore .wrangler
alongside dist so local preview artifacts are not accidentally committed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/cloudflare/package.json`:
- Around line 16-22: The package metadata still points `exports["."]` and
`types` at `src/index.ts`, but only `dist/` is published, so update the
Cloudflare adapter package entrypoints to the built outputs instead. Adjust the
`exports` and `types` fields in the package configuration to reference the
generated JS and declaration files under `dist/`, keeping the existing `files`
list aligned with what consumers can actually import.

In `@packages/adapters/cloudflare/src/index.ts`:
- Around line 74-83: The Workers preview path in startPreview() is pointing at a
dist-local wrangler.json that buildEnd() may not create when a root Wrangler
config already exists. Update the preview flow in startPreview() and the config
emission logic in buildEnd() so they stay in sync: either always write the
dist-scoped wrangler.json, or have the preview command reference the root config
when that is the intended source. Use the existing startPreview and buildEnd
symbols to locate the related branches and keep the pages/dev and worker/dev
modes consistent.
- Around line 68-90: The startPreview flow in the Cloudflare adapter is dropping
env-file support because it spawns Wrangler with only process.env. Update the
startPreview implementation in the Cloudflare adapter so it merges
previewOptions.options.envFile into the environment passed to spawn, matching
the base adapter’s env-file handling. Use the existing startPreview,
previewOptions, and spawn setup to locate the change and ensure Wrangler preview
receives the loaded env values.

---

Nitpick comments:
In `@packages/adapters/cloudflare/scripts/build.ts`:
- Around line 40-44: The single-entry ESM build in build() is using splitting:
true, which deviates from the sibling script’s convention and could change the
emitted dist layout if dynamic imports appear later. Update the cloudflare build
configuration to match the existing pattern used in the analogous build script
by disabling splitting for the src/index.ts ESM output, keeping the build
behavior aligned and stable.

In `@packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore`:
- Line 1: The fixture .gitignore currently ignores only dist, but wrangler dev
and wrangler pages dev also create a local .wrangler cache directory that should
be excluded. Update the .gitignore content in this fixture to ignore .wrangler
alongside dist so local preview artifacts are not accidentally committed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0821cb7-cb32-48f5-83ad-6704392ddba9

📥 Commits

Reviewing files that changed from the base of the PR and between 779776f and d0aa640.

⛔ Files ignored due to path filters (3)
  • package-lock.json is excluded by !**/package-lock.json and included by **
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
📒 Files selected for processing (26)
  • .changeset/cloudflare-adapter.md
  • cspell.json
  • packages/adapters/cloudflare/README.md
  • packages/adapters/cloudflare/package.json
  • packages/adapters/cloudflare/scripts/build.ts
  • packages/adapters/cloudflare/scripts/importMetaURL.js
  • packages/adapters/cloudflare/src/default-entry.ts
  • packages/adapters/cloudflare/src/index.ts
  • packages/adapters/cloudflare/src/types.ts
  • packages/adapters/cloudflare/tsconfig.json
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.marko-run/routes.d.ts
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/package.json
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/components/counter.marko
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/routes/+page.marko
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/test.config.ts
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/tsconfig.json
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/vite.config.ts
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.gitignore
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.marko-run/routes.d.ts
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/package.json
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/components/counter.marko
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/routes/+page.marko
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/test.config.ts
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/tsconfig.json
  • packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/vite.config.ts

Comment thread packages/adapters/cloudflare/package.json
Comment thread packages/adapters/cloudflare/src/index.ts Outdated
Comment thread packages/adapters/cloudflare/src/index.ts
- Add package.toggle.json so publish points exports/types at dist/
- Make the injected import.meta.url shim a string (.href) for the CJS build
- Fix the README platform example to read from context.platform
- Workers preview: only pass --config when the generated wrangler.json
  exists (it is skipped when the project has its own Wrangler config),
  otherwise let Wrangler discover the project config
- Load the --env-file into the Wrangler preview environment
- Throw instead of exiting the process when the Wrangler CLI is missing

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/adapters/cloudflare/src/index.ts (1)

83-100: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Drop shell: true from the Wrangler spawn.

spawn(..., { shell: true }) concatenates args instead of escaping them, so publicDir, generatedConfig, and forwarded preview args can be split or interpreted by the shell. Use shell: false (or a cross-platform wrapper if you need shim support).

♻️ Proposed change
       const proc = spawn("wrangler", args, {
         cwd,
         env: process.env,
-        shell: true,
+        shell: false,
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/cloudflare/src/index.ts` around lines 83 - 100, The
Wrangler launch in spawn currently uses shell: true, which can let publicDir,
generatedConfig, and args from parseWranglerArgs be reinterpreted by the shell.
Update the spawn call in the Cloudflare adapter’s index logic to run without a
shell (or use a cross-platform wrapper if shim support is required) so the args
array is passed through safely and verbatim.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/adapters/cloudflare/src/index.ts`:
- Around line 83-100: The Wrangler launch in spawn currently uses shell: true,
which can let publicDir, generatedConfig, and args from parseWranglerArgs be
reinterpreted by the shell. Update the spawn call in the Cloudflare adapter’s
index logic to run without a shell (or use a cross-platform wrapper if shim
support is required) so the args array is passed through safely and verbatim.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e25ac50-08e8-43cc-bbd2-cbc594375514

📥 Commits

Reviewing files that changed from the base of the PR and between d0aa640 and 253219f.

📒 Files selected for processing (4)
  • packages/adapters/cloudflare/README.md
  • packages/adapters/cloudflare/package.toggle.json
  • packages/adapters/cloudflare/scripts/importMetaURL.js
  • packages/adapters/cloudflare/src/index.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/adapters/cloudflare/package.toggle.json
  • packages/adapters/cloudflare/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/adapters/cloudflare/scripts/importMetaURL.js

rturnq added 2 commits July 1, 2026 16:05
Use `shell: true` only on Windows (where `wrangler` is a `.cmd` shim that
requires it). On POSIX the CLI is spawned directly, so `close()` kills
Wrangler itself instead of an orphaned shell wrapper and argument values
are no longer re-parsed by the shell. Also handle the child's error event
so a failed launch doesn't crash the parent.
…lare)

Clarify that Marko Run automatically uses an installed adapter with no
Vite config, and add a Deploying section right after installation showing
`npm run build` + `npx wrangler deploy -c dist/wrangler.json`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant