Skip to content

feat: framework integrations (Astro, SvelteKit, Waku) without wrangler#74

Open
john-royal wants to merge 37 commits into
mainfrom
claude/framework-integrations
Open

feat: framework integrations (Astro, SvelteKit, Waku) without wrangler#74
john-royal wants to merge 37 commits into
mainfrom
claude/framework-integrations

Conversation

@john-royal

@john-royal john-royal commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Framework integrations for Astro, SvelteKit, and Waku with no wrangler dependency and no wrangler config files anywhere. (Next.js is split out to #82.)

// framework-core: platform-neutral orchestration; the deploy target is a value
interface DeployTarget<Config> {
  platform: string;
  config: Config;                 // opaque target carriage
  bundle?: { conditions, external, mainFields };  // resolve/bundle settings
  build?: (ctx) => Effect<BuildOutput>;   // wholesale takeover (OpenNext case)
  finish?: (output, ctx) => Effect<BuildOutput>;  // post-adapt finishing pass
  serve?: (ctx) => Effect<{ url }, ..., Scope>;   // local serving of built output
}

Architecture: frameworks × deploy targets

Each framework package (waku, astro, sveltekit) is split into platform-neutral orchestration and a Cloudflare target module at the ./cloudflare subpath (adapter forks, vite-plugin injection, workerd dev). Framework options take target as a prop; per-package import-boundary tests enforce zero cloudflare imports outside the target modules. Future AWS targets slot in as sibling target modules without touching the framework halves — groundwork for AWS website resources.

  • framework-coreBuildOutput contract, collector, loaders, Framework service, DeployTarget.
  • waku — programmatic vite build/dev, wrangler-free adapter fork, SSG.
  • astro — forked createIntegration; vendored runtime entrypoints (neither wrangler nor @cloudflare/vite-plugin enter the tree).
  • sveltekit — in-memory kit adapter + rolldown finishing pass.
  • Each ships a ./source entry implementing alchemy's WorkerSourceModule contract structurally.

Fixtures

Deep per-framework coverage (live + dev playwright): waku — server actions, dynamic routes, API routes, hydration, SSG (20 tests); astro — middleware/locals, redirects, binary endpoints, content collections, islands (18); sveltekit — form actions with and without JS, cookie loads, route groups, binary endpoints, prerender+SSR mix (22); plus vocs (Waku-based docs framework) riding the waku cloudflare target with two documented workerd bridges (9); and the five pre-existing fixtures.

Supporting changes

  • cloudflare-runtime: PlatformProxy.open / getPlatformProxy (wrangler-free platform proxy), KvNamespace.local, Service.self(), workerd exception capture, Windows socket-churn fixes.
  • cloudflare-vite-plugin / cloudflare-rolldown-plugin: skipEnvironments, guarded buildApp default, dev.middlewareOrder.
  • tools/e2e: pluggable Framework services; target-scoped config carriage (options.target, legacy vite/miniflare aliases kept); harness owns dist/build.jsonFramework.build() is pure.
  • upstream/: workers-sdk moved under upstream/; shallow submodules for next.js, opennextjs-cloudflare, astro, sveltekit, waku.
  • Research docs and spike fixtures retired — durable content absorbed into package READMEs and AGENTS.md; history keeps the rest.

CI green on ubuntu/macos/windows (fixture e2e serialized on Windows; sveltekit fixture gated there — documented runner-level socket exhaustion).

🤖 Generated with Claude Code

john-royal and others added 16 commits July 20, 2026 12:40
- Move workers-sdk submodule to upstream/workers-sdk
- Add shallow submodules: upstream/next.js, upstream/opennextjs-cloudflare,
  upstream/astro, upstream/sveltekit, upstream/waku
- Update path references in docs, lint/format configs, and code comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Research specs for Next.js, @opennextjs/cloudflare, Astro, SvelteKit, Waku
(programmatic dev/build hooks, Cloudflare integration, wrangler coupling,
adaptation plans), internal context maps for cloudflare-tools and alchemy's
Website surface, and the synthesized phased implementation plan (PLAN.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, SvelteKit, Astro

All four spikes proved-with-caveats:
- Next: OpenNext programmatic build (2-field config stub) + esbuild final
  pass + boot in cloudflare-runtime, 11/11 HTTP checks; same-script SQLite
  DOs / self service binding / ASSETS all work today
- Waku: input-merge risk disproven; plugin must ride waku's config.vite.plugins
  (or fix dev middleware ordering); collector needs post-buildApp disk re-read
- SvelteKit: in-memory adapter + zero-config rolldown re-bundle, 9/9 boot checks
- Astro: our-plugin-inside-astro validated; needs env scoping + optionsPlugin
  buildApp guard in our plugins

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dering

- skipEnvironments option: applyToEnvironment on per-env plugins,
  configEnvironment gating, nodejs-compat tolerant of no-discovery
  optimizers (unblocks Astro's node-side astro/prerender envs)
- optionsPlugin only defaults builder.buildApp when the user config
  doesn't define one (Astro owns its own orchestrator)
- dev.middlewareOrder "pre" hoists the workerd proxy ahead of framework
  post-middlewares via an enforce:pre boundary plugin (unblocks Waku
  append-last composition); default remains "post"
- dev-environment degrades gracefully when configureServer is stripped
  (Astro typegen temp servers); HotChannel close hardening

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Service.self(name): same-script self service binding
  (WORKER_SELF_REFERENCE); bypasses the assets middleware chain
- RuntimeWorker.logging { verbose, onOutput }: surface workerd's
  exception output instead of invisible bare 500s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, Framework service

New package @distilled.cloud/framework-core: BuildOutput types +
dist/build.json persistence, collector plugin (skipEnvironments,
deterministic entry selection, post-buildApp disk re-read), project
module loaders, and the effectful Framework service contract.
tools/e2e refactored onto it with no behavior change (fixture e2e
suites verified identical).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e2e.config.ts gains an optional framework field (package specifier,
factory, or Layer) resolved from the fixture's node_modules; dev/build/
preview dispatch through the framework-core Framework service, with the
built-in Vite implementation as the default. Contract documented in
README.md. Also fixes an ESM cycle (Cwd extracted from Runtime).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getPlatformProxy equivalent (maintainer policy: reimplemented here, never
a wrangler dependency): PlatformProxy.open (Effect) + getPlatformProxy
(Promise) return Node-side env/cf/ctx/caches proxies over an internal
proxy worker; caches actually round-trip. Plus KvNamespace.local, an
in-memory KV plugin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Framework-service Layer over waku's programmatic vite APIs with a
wrangler-free cloudflare adapter fork; cloudflareVitePlugin injected
inside wakuConfig.vite.plugins; SSG via __WAKU_START_PREVIEW_SERVER__.
Fixture: SSR RSC + binding, SSG, hydration, static assets — 10/10 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forked createIntegration running our vite plugin inside astro's
build()/dev() (skipEnvironments for astro/prerender envs); vendored
runtime entrypoints + virtual-config plugin so neither wrangler nor
@cloudflare/vite-plugin enter the tree. Node prerendering, passthrough
image service. Fixture: SSR + binding, prerender, 404-via-ASSETS,
hydration — 10/10 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In-memory kit adapter (generated shim with real import paths, no
wrangler config) + post-adapt rolldown pass via cloudflare-rolldown-plugin
producing workerd-ready entry-first modules. Dev v1: kit Node SSR with a
documented stub platform (real bindings arrive with the platform proxy).
Fixture: SSR platform.env, endpoint, prerender, hydration — 10/10 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenNext build via child-process deep import with a 2-field in-memory
config and an inert wrangler stub (no wrangler code ever executes);
esbuild finishing pass (workerd conditions, lazy handler chunk,
createRequire banner, wasm/bin module rules); static-assets
populateCache; dev v1 = preview parity via Runtime.start with
Service.self + same-script SQLite DOs. Fixture: SSR, API+middleware,
ISR serve, assets, hydration — 16/16 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alchemy-version-bot

alchemy-version-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

@distilled.cloud/cloudflare-rolldown-plugin

bun add https://pkg.distilled.cloud/cloudflare-rolldown-plugin/3280c29

@distilled.cloud/cloudflare-runtime

bun add https://pkg.distilled.cloud/cloudflare-runtime/3280c29

@distilled.cloud/cloudflare-vite-plugin

bun add https://pkg.distilled.cloud/cloudflare-vite-plugin/3280c29

Each package exports './source': a structural implementation of alchemy's
WorkerSourceModule contract wrapping its Framework service — build()
(entry-first bundle + asset manifest + version-salted input hash),
hash() (rebuild-free), dev() (server-mode handle). No alchemy imports;
errors raise a structural twin of Cloudflare.Workers.SourceProviderError.
Also promotes waku's cloudflare-runtime dep out of devDependencies
(build-order + publish correctness).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
john-royal and others added 11 commits July 20, 2026 20:52
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Module names in a BuildOutput become workerd module names and worker
upload paths, so they must be /-separated regardless of platform.
NodePath.join/relative produce backslashes on Windows, which failed 8
Collector tests (and would have produced invalid worker uploads).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…detection

Vite module ids are POSIX even on Windows; comparing them against a
backslashed resolved root spuriously classified in-root modules as
external workspaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows keeps workerd's file locks alive briefly after shutdown; retry
the scoped temp-directory removal and prefer leaking it over failing
dispose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows CI runs every fixture's playwright suite concurrently on a
4-core runner; two consecutive runs failed on two different fixtures
with unrelated transient errors (dev-server dep-optimizer reload race,
chromium launch death). Retries are CI-only; local runs stay strict.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The OpenNext build copies NFT-traced node_modules built from bun-store
junctions; the copy leaves them broken on Windows and esbuild fails with
'Access is denied'. Documented in scripts/e2e.mjs; macOS/Linux unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Concurrent workerd/playwright fixture suites exhaust the Windows
runner's socket buffer space (WSAENOBUFS on bind); run test tasks with
--concurrency=1 there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows CI exhausted AFD/ephemeral-port buffers late in the job (bind
WSAENOBUFS, ConnectEx ERROR_DUP_NAME inside workerd). Our contributions,
now fixed:

- LoopbackServer + vite dev module-fallback server: 60s keepAliveTimeout
  (Node's 5s default closed workerd's pooled connections every idle gap,
  churning a TIME_WAIT socket per subrequest); finalizers hard-close
  connections
- Port scan: classify bind errors by code — ENOBUFS/EMFILE now fail fast
  with a typed PortBindFailed instead of probing 7 hosts across the
  whole port range; search bounded to 128 candidates
- WorkerProxy.serveWithRetry: bounded to 8 workerd spawns (a crash under
  exhaustion was classified address-in-use and respawned unbounded)
- get-address: toConnectableHost normalizes 0.0.0.0/:: so a listen-all
  bind is never handed to workerd as a connect target (invalid on
  Windows); cross-platform tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
john-royal and others added 4 commits July 20, 2026 22:43
Runner-level socket exhaustion (WSAENOBUFS) persists from components
outside this repo (chromium, miniflare loopback churn) even after the
cloudflare-runtime fixes; documented in scripts/e2e.mjs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…json persistence from the Framework contract

Two concurrent change sets:

- nextjs dev v2 groundwork (DevServer.ts): programmatic next({ dev: true }) +
  prepare() on a caller-owned http.Server, with cloudflare-runtime's
  PlatformProxy planted on OpenNext's
  globalThis[Symbol.for("__cloudflare-context__")] contract (env/cf/ctx) and
  the vm.runInContext patch for the edge-runtime sandbox — so
  getCloudflareContext() works in next dev without
  initOpenNextCloudflareForDev and without wrangler. Plus the package README
  documenting preview/hmr dev-mode fidelity.
- framework-core contract simplification: Framework.build is in-memory only
  (no dist/build.json side effect), readBuildOutput removed from the service;
  the e2e harness persists build.json itself, framework packages
  (astro/sveltekit/waku/nextjs/vite) updated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gs, wrangler-free)

Wire DevServer into the Framework service as options.dev.mode:
"preview" (default, unchanged v1 workerd preview) | "hmr". The source
descriptor (NextjsSourceOptions) surfaces the same JSON-stable dev.mode.

fixtures/nextjs: hmr-mode dev spec (test/hmr.test.ts) driving the Framework
service directly — page serves through next dev, getCloudflareContext().env
binding value visible in SSR output and an API route, and a page edit is
reflected on a subsequent request (bounded Effect retry, file restored in a
finally). playwright build.external excludes .next/** so playwright's babel
require-hook doesn't choke on Turbopack's sectioned source maps;
scripts/hmr-repro.mjs is a minimal manual runner for the same path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Next.js/OpenNext integration needs more iteration (bundler
consolidation, dependency internalization, the coming adapters-api
migration) — moved to its own branch + draft PR so the Waku/Astro/
SvelteKit integrations can land independently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@john-royal john-royal changed the title feat: framework integrations (Next.js, Astro, SvelteKit, Waku) without wrangler feat: framework integrations (Astro, SvelteKit, Waku) without wrangler Jul 22, 2026
john-royal and others added 5 commits July 24, 2026 12:12
…zation

The deploy target (conditions, finishing pass, dev serving, config
carriage) becomes a value passed to framework packages; Cloudflare is
the first target implementation (harness CloudflareTarget). Groundwork
for future AWS targets — framework orchestration stays platform-neutral.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Their durable content now lives in package READMEs and AGENTS.md
(architecture, doctrine, per-framework limitations); the rest is git
history. Also removes the untracked build-artifact leftovers from the
split-out nextjs fixture and prunes the related lint ignores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vocs 2.6 is Waku-based; the fixture drives its plugin stack through the
refactored @distilled.cloud/waku/cloudflare target hooks with two
fixture-local workerd bridges (build-time config resolution; no
new Function in config revival). 9/9 playwright (live + dev).
Also finishes the spike/research retirement (on-disk artifact removal,
lint-ignore pruning).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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