diff --git a/.changeset/cloudflare-adapter.md b/.changeset/cloudflare-adapter.md
new file mode 100644
index 00000000..518ecb70
--- /dev/null
+++ b/.changeset/cloudflare-adapter.md
@@ -0,0 +1,5 @@
+---
+"@marko/run-adapter-cloudflare": minor
+---
+
+Add a Cloudflare adapter for deploying @marko/run apps to Cloudflare Workers (default) or Cloudflare Pages.
diff --git a/cspell.json b/cspell.json
index 21f9d12d..e5973e9e 100644
--- a/cspell.json
+++ b/cspell.json
@@ -14,6 +14,7 @@
"Rels",
"onopentag",
"WHATWG",
+ "workerd",
"rgba",
"Neue",
"Roboto",
diff --git a/package-lock.json b/package-lock.json
index a01363b5..82313657 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -673,6 +673,12 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/@cloudflare/workers-types": {
+ "version": "4.20260630.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260630.1.tgz",
+ "integrity": "sha512-yl+c9vwvko9UZ0frmtsHuwOh3BRHvNjLrfelAp5Akpqe1+Ho1UWekr3nmjJ1D64CH0Yb0K0oRMV4i7npOFzsog==",
+ "license": "MIT OR Apache-2.0"
+ },
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -2485,6 +2491,10 @@
"resolved": "packages/run",
"link": true
},
+ "node_modules/@marko/run-adapter-cloudflare": {
+ "resolved": "packages/adapters/cloudflare",
+ "link": true
+ },
"node_modules/@marko/run-adapter-netlify": {
"resolved": "packages/adapters/netlify",
"link": true
@@ -14038,6 +14048,381 @@
}
}
},
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true
+ },
+ "node_modules/netlify-cli/node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true
+ },
"node_modules/netlify-cli/node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"dev": true,
@@ -17283,6 +17668,19 @@
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/netlify-cli/node_modules/fsevents": {
+ "version": "2.3.3",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/netlify-cli/node_modules/function-bind": {
"version": "1.1.2",
"dev": true,
@@ -21065,6 +21463,53 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/netlify-cli/node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/netlify-cli/node_modules/router": {
"version": "2.2.0",
"dev": true,
@@ -25556,6 +26001,17 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
+ "packages/adapters/cloudflare": {
+ "name": "@marko/run-adapter-cloudflare",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@cloudflare/workers-types": "^4.20241106.0"
+ },
+ "peerDependencies": {
+ "@marko/run": "^0.7||^0.8||^0.9||^0.10"
+ }
+ },
"packages/adapters/netlify": {
"name": "@marko/run-adapter-netlify",
"version": "3.0.3",
diff --git a/packages/adapters/cloudflare/README.md b/packages/adapters/cloudflare/README.md
new file mode 100644
index 00000000..29c4c62c
--- /dev/null
+++ b/packages/adapters/cloudflare/README.md
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+ @marko/run-adapter-cloudflare
+
+
+
+Preview and deploy [@marko/run](../../run/README.md) apps to Cloudflare Workers/Pages
+
+## Installation
+
+```sh
+npm install @marko/run-adapter-cloudflare
+```
+
+That's all the setup required — Marko Run automatically discovers an installed adapter and uses it, so you **don't** need to register it in your Vite config. See [Configuration](#configuration) if you want to build for Cloudflare Pages instead of the default Workers target.
+
+`wrangler` is used to preview and deploy your app; install it as a dev dependency:
+
+```sh
+npm install -D wrangler
+```
+
+## Deploying
+
+Build your app and deploy the output with the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/):
+
+```sh
+npm run build
+npx wrangler deploy -c dist/wrangler.json
+```
+
+`marko-run build` produces the following in your output directory (`dist` by default):
+
+- `_worker.js` — the bundled server entry
+- `public/` — your app's static assets
+- `wrangler.json` — a generated starter config (only written when no `wrangler.toml`/`wrangler.json`/`wrangler.jsonc` exists at your project root)
+
+To use your own bindings (KV, D1, R2, secrets, custom name, routes, etc.), add a Wrangler config at the root of your project. When present, the adapter will **not** generate one — point it at the build output yourself and deploy with `npx wrangler deploy`:
+
+```jsonc
+// wrangler.jsonc
+{
+ "name": "my-app",
+ "main": "./dist/_worker.js",
+ "compatibility_date": "2024-11-01",
+ "compatibility_flags": ["nodejs_compat"],
+ "assets": {
+ "directory": "./dist/public",
+ "binding": "ASSETS",
+ },
+}
+```
+
+## Configuration
+
+Marko Run uses this adapter automatically, but you can register it in your Vite config (eg. `vite.config.js`) to pass options — for example, to target [Cloudflare Pages](https://developers.cloudflare.com/pages/) instead of the default [Workers](https://developers.cloudflare.com/workers/):
+
+```ts
+import { defineConfig } from "vite";
+import marko from "@marko/run/vite";
+import cloudflareAdapter from "@marko/run-adapter-cloudflare";
+
+export default defineConfig({
+ plugins: [
+ marko({
+ adapter: cloudflareAdapter({ mode: "pages" }),
+ }),
+ ],
+});
+```
+
+In `"pages"` mode the adapter builds a [Pages Functions "advanced mode"](https://developers.cloudflare.com/pages/functions/advanced-mode/) `_worker.js` and a `_routes.json` into `dist/public`. Deploy the assets directory instead:
+
+```sh
+npm run build
+npx wrangler pages deploy dist/public
+```
+
+## Platform info
+
+The Worker environment, execution context and Cloudflare request properties are available on the `platform` argument of your route handlers:
+
+```ts
+import type { CloudflarePlatformInfo } from "@marko/run-adapter-cloudflare";
+
+export const GET = (context) => {
+ const { env, ctx, cf } = context.platform as CloudflarePlatformInfo;
+ ctx.waitUntil(logRequest(cf));
+ return new Response(`Hello from ${cf?.city ?? "the edge"}!`);
+};
+```
diff --git a/packages/adapters/cloudflare/package.json b/packages/adapters/cloudflare/package.json
new file mode 100644
index 00000000..8f3978db
--- /dev/null
+++ b/packages/adapters/cloudflare/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@marko/run-adapter-cloudflare",
+ "version": "0.1.0",
+ "description": "Preview and deploy @marko/run apps to Cloudflare Workers/Pages",
+ "keywords": [
+ "marko"
+ ],
+ "homepage": "https://github.com/marko-js/run/tree/main/packages/adapters/cloudflare",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/marko-js/run.git"
+ },
+ "license": "MIT",
+ "author": "Ryan Turnquist ",
+ "type": "module",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "types": "./src/index.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc -b && tsx scripts/build.ts",
+ "test": ""
+ },
+ "dependencies": {
+ "@cloudflare/workers-types": "^4.20241106.0"
+ },
+ "peerDependencies": {
+ "@marko/run": "^0.7||^0.8||^0.9||^0.10"
+ },
+ "logo": {
+ "url": "https://github.com/marko-js/run/raw/main/assets/marko-run.png"
+ }
+}
diff --git a/packages/adapters/cloudflare/package.toggle.json b/packages/adapters/cloudflare/package.toggle.json
new file mode 100644
index 00000000..a2f5767a
--- /dev/null
+++ b/packages/adapters/cloudflare/package.toggle.json
@@ -0,0 +1,15 @@
+{
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ }
+ },
+ "types": "./dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "*": ["./dist/index.d.ts"]
+ }
+ }
+}
diff --git a/packages/adapters/cloudflare/scripts/build.ts b/packages/adapters/cloudflare/scripts/build.ts
new file mode 100644
index 00000000..d70f08ec
--- /dev/null
+++ b/packages/adapters/cloudflare/scripts/build.ts
@@ -0,0 +1,51 @@
+import { build, BuildOptions } from "esbuild";
+import path from "path";
+
+const srcdir = path.resolve("src");
+const outdir = path.resolve("dist");
+
+const opts: BuildOptions = {
+ entryPoints: ["src/index.ts"],
+ outdir,
+ outbase: srcdir,
+ platform: "node",
+ target: ["node14"],
+ bundle: true,
+ plugins: [
+ {
+ name: "external-modules",
+ setup(build) {
+ build.onResolve(
+ { filter: /^[^./]|^\.[^./]|^\.\.[^/]/ },
+ ({ path }) => ({
+ path,
+ external: true,
+ }),
+ );
+ },
+ },
+ ],
+};
+
+await Promise.all([
+ build({
+ ...opts,
+ format: "cjs",
+ outExtension: { ".js": ".cjs" },
+ define: {
+ "import.meta.url": "__importMetaURL",
+ },
+ inject: ["./scripts/importMetaURL.js"],
+ }),
+ build({
+ ...opts,
+ format: "esm",
+ splitting: true,
+ }),
+ build({
+ ...opts,
+ entryPoints: ["src/default-entry.ts"],
+ format: "esm",
+ outExtension: { ".js": ".mjs" },
+ }),
+]);
diff --git a/packages/adapters/cloudflare/scripts/importMetaURL.js b/packages/adapters/cloudflare/scripts/importMetaURL.js
new file mode 100644
index 00000000..b1277af3
--- /dev/null
+++ b/packages/adapters/cloudflare/scripts/importMetaURL.js
@@ -0,0 +1,2 @@
+import { pathToFileURL } from "url";
+export const __importMetaURL = pathToFileURL(__filename).href;
diff --git a/packages/adapters/cloudflare/src/default-entry.ts b/packages/adapters/cloudflare/src/default-entry.ts
new file mode 100644
index 00000000..983317d3
--- /dev/null
+++ b/packages/adapters/cloudflare/src/default-entry.ts
@@ -0,0 +1,30 @@
+import type {
+ ExecutionContext,
+ Request as CfRequest,
+} from "@cloudflare/workers-types";
+import { fetch } from "@marko/run/router";
+
+import type { CloudflareEnv, CloudflarePlatformInfo } from "./types";
+
+export default {
+ async fetch(request: CfRequest, env: CloudflareEnv, ctx: ExecutionContext) {
+ const response = await fetch(request as any, {
+ env,
+ ctx,
+ cf: request.cf,
+ });
+
+ if (response) {
+ return response;
+ }
+
+ // Fall back to the static asset handler when no route matched. This is a
+ // safety net — Cloudflare normally serves matching assets before invoking
+ // the worker.
+ if (env.ASSETS) {
+ return env.ASSETS.fetch(request);
+ }
+
+ return new Response(null, { status: 404 });
+ },
+};
diff --git a/packages/adapters/cloudflare/src/index.ts b/packages/adapters/cloudflare/src/index.ts
new file mode 100644
index 00000000..3ac1c017
--- /dev/null
+++ b/packages/adapters/cloudflare/src/index.ts
@@ -0,0 +1,255 @@
+import baseAdapter, { type Adapter } from "@marko/run/adapter";
+import { loadEnv } from "@marko/run/vite";
+import { execSync, spawn } from "child_process";
+import { existsSync } from "fs";
+import fs from "fs/promises";
+import path from "path";
+import { fileURLToPath } from "url";
+
+export type { CloudflareEnv, CloudflarePlatformInfo } from "./types";
+
+const __dirname = fileURLToPath(path.dirname(import.meta.url));
+const defaultEntry = path.join(__dirname, "default-entry");
+
+/** Generated when no Wrangler config is found at the project root. */
+const COMPATIBILITY_DATE = "2024-11-01";
+
+export interface Options {
+ /**
+ * The Cloudflare product to target.
+ *
+ * - `"workers"` (default) builds a Worker with a static assets binding,
+ * deployed with `wrangler deploy`.
+ * - `"pages"` builds a Cloudflare Pages "advanced mode" `_worker.js`
+ * alongside the static assets, deployed with `wrangler pages deploy`.
+ */
+ mode?: "workers" | "pages";
+}
+
+export default function cloudflareAdapter(options: Options = {}): Adapter {
+ const { mode = "workers" } = options;
+ const { startDev } = baseAdapter();
+
+ return {
+ name: "cloudflare-adapter",
+
+ viteConfig(config) {
+ if (config.build?.ssr) {
+ return {
+ ssr: {
+ target: "webworker",
+ resolve: {
+ dedupe: ["marko"],
+ conditions: [
+ "workerd",
+ "worker",
+ "browser",
+ "import",
+ "require",
+ "production",
+ "default",
+ ],
+ },
+ noExternal: true,
+ },
+ };
+ }
+ },
+
+ getEntryFile() {
+ return defaultEntry;
+ },
+
+ startDev(event) {
+ return startDev!({
+ ...event,
+ entry: event.entry === defaultEntry ? undefined : event.entry,
+ });
+ },
+
+ async startPreview({ options: previewOptions }) {
+ assertWranglerCLI();
+
+ const { port = 3000, cwd, dir, envFile } = previewOptions;
+ if (envFile) {
+ await loadEnv(envFile);
+ }
+
+ const publicDir = path.join(dir, "public");
+ // buildEnd only generates this when the project has no Wrangler config of
+ // its own; otherwise let Wrangler discover the project's config.
+ const generatedConfig = path.join(dir, "wrangler.json");
+
+ const args =
+ mode === "pages"
+ ? ["pages", "dev", publicDir, "--port", port.toString()]
+ : [
+ "dev",
+ ...(existsSync(generatedConfig)
+ ? ["--config", generatedConfig]
+ : []),
+ "--port",
+ port.toString(),
+ ];
+
+ args.push(...parseWranglerArgs(previewOptions.args));
+
+ // Spawn through a shell only on Windows, where `wrangler` is a `.cmd`
+ // shim that can't be spawned directly. On POSIX, spawning directly means
+ // `close()` terminates Wrangler itself (not a shell wrapper) and avoids
+ // the shell re-parsing the argument values.
+ const proc = spawn("wrangler", args, {
+ cwd,
+ env: process.env,
+ shell: process.platform === "win32",
+ });
+
+ proc.on("error", (err) => {
+ console.error("Failed to start wrangler preview server:", err);
+ });
+
+ if (process.env.NODE_ENV !== "test") {
+ proc.stdout.pipe(process.stdout);
+ }
+ proc.stderr.pipe(process.stderr);
+
+ return {
+ port,
+ close() {
+ proc.unref();
+ proc.kill();
+ },
+ };
+ },
+
+ async buildEnd({ config, builtEntries }) {
+ const serverEntry = builtEntries[0];
+ if (!serverEntry) {
+ return;
+ }
+
+ // During the client build `outDir` points at the static assets dir
+ // (`/public`); its parent is the SSR build dir that holds the
+ // built worker entry.
+ const publicDir = path.resolve(config.root, config.build.outDir);
+ const distDir = path.dirname(publicDir);
+
+ if (mode === "pages") {
+ // Advanced mode: a single `_worker.js` sits next to the static assets
+ // and a `_routes.json` keeps static files from invoking the worker.
+ const exclude = await collectStaticRoutes(publicDir);
+ await fs.rename(serverEntry, path.join(publicDir, "_worker.js"));
+ await fs.writeFile(
+ path.join(publicDir, "_routes.json"),
+ JSON.stringify({ version: 1, include: ["/*"], exclude }, null, 2),
+ );
+ } else {
+ // Workers: keep the worker bundle in the dist root and point a
+ // generated Wrangler config at it and the static assets. An existing
+ // project-level Wrangler config is left untouched.
+ const workerEntry = path.join(distDir, "_worker.js");
+ if (path.resolve(serverEntry) !== workerEntry) {
+ await fs.rename(serverEntry, workerEntry);
+ }
+
+ if (!(await hasRootWranglerConfig(config.root))) {
+ await fs.writeFile(
+ path.join(distDir, "wrangler.json"),
+ JSON.stringify(
+ {
+ name: "marko-run-app",
+ main: "_worker.js",
+ compatibility_date: COMPATIBILITY_DATE,
+ compatibility_flags: ["nodejs_compat"],
+ assets: {
+ directory: "./public",
+ binding: "ASSETS",
+ },
+ },
+ null,
+ 2,
+ ),
+ );
+ }
+ }
+ },
+
+ typeInfo(writer) {
+ writer(
+ `import type { CloudflarePlatformInfo } from '@marko/run-adapter-cloudflare';`,
+ );
+ return "CloudflarePlatformInfo";
+ },
+ };
+}
+
+/**
+ * Build a `_routes.json` exclude list from the top-level entries of the static
+ * assets directory so Cloudflare Pages serves them directly instead of
+ * invoking the worker. Cloudflare allows at most 100 exclude rules.
+ */
+async function collectStaticRoutes(publicDir: string): Promise {
+ let entries: import("fs").Dirent[];
+ try {
+ entries = await fs.readdir(publicDir, { withFileTypes: true });
+ } catch {
+ return [];
+ }
+
+ const exclude: string[] = [];
+ for (const entry of entries) {
+ if (entry.name === "_worker.js" || entry.name === "_routes.json") {
+ continue;
+ }
+ exclude.push(entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`);
+ if (exclude.length >= 100) {
+ break;
+ }
+ }
+ return exclude;
+}
+
+async function hasRootWranglerConfig(root: string): Promise {
+ for (const name of ["wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
+ try {
+ await fs.access(path.join(root, name));
+ return true;
+ } catch {
+ // not found, keep looking
+ }
+ }
+ return false;
+}
+
+const devFlags = new RegExp(
+ [
+ "compatibility-date=.+",
+ "compatibility-flags=.+",
+ "env=.+",
+ "ip=.+",
+ "local-protocol=.+",
+ "https-key-path=.+",
+ "https-cert-path=.+",
+ "inspector-port=.+",
+ "live-reload",
+ "remote",
+ "local",
+ "show-interactive-dev-session",
+ ]
+ .map((flag) => `--${flag}`)
+ .join("|"),
+);
+
+function parseWranglerArgs(args: string[]) {
+ return args.filter((arg) => devFlags.test(arg));
+}
+
+function assertWranglerCLI() {
+ try {
+ execSync("wrangler --version");
+ } catch {
+ throw new Error(
+ "Wrangler CLI not found. Please install it with `npm install -D wrangler`.",
+ );
+ }
+}
diff --git a/packages/adapters/cloudflare/src/types.ts b/packages/adapters/cloudflare/src/types.ts
new file mode 100644
index 00000000..8f957d26
--- /dev/null
+++ b/packages/adapters/cloudflare/src/types.ts
@@ -0,0 +1,27 @@
+import type {
+ ExecutionContext,
+ Fetcher,
+ IncomingRequestCfProperties,
+} from "@cloudflare/workers-types";
+
+/**
+ * The bindings available to the worker. The `ASSETS` binding is provided
+ * automatically by Cloudflare Pages and by Workers configured with a static
+ * assets directory, and is used to serve the app's static files.
+ */
+export interface CloudflareEnv {
+ ASSETS: Fetcher;
+ [key: string]: unknown;
+}
+
+/**
+ * The platform object passed to Marko Run route handlers when deployed to
+ * Cloudflare. Exposes the Worker's environment bindings, the execution
+ * context (for `waitUntil`/`passThroughOnException`) and the Cloudflare
+ * request properties (`request.cf`).
+ */
+export interface CloudflarePlatformInfo {
+ env: Env;
+ ctx: ExecutionContext;
+ cf?: IncomingRequestCfProperties;
+}
diff --git a/packages/adapters/cloudflare/tsconfig.json b/packages/adapters/cloudflare/tsconfig.json
new file mode 100644
index 00000000..cd4f0966
--- /dev/null
+++ b/packages/adapters/cloudflare/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../../tsconfig.json",
+ "include": ["src/**/*"],
+ "exclude": ["src/**/__tests__/**/*", "src/default-entry.ts"],
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "tsBuildInfoFile": "dist/.tsbuildinfo"
+ }
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore
new file mode 100644
index 00000000..1521c8b7
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.gitignore
@@ -0,0 +1 @@
+dist
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.marko-run/routes.d.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.marko-run/routes.d.ts
new file mode 100644
index 00000000..fb83b4f5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/.marko-run/routes.d.ts
@@ -0,0 +1,36 @@
+/*
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
+ Do NOT manually edit this file or your changes will be lost.
+*/
+
+import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";
+import type * as Run from "@marko/run";
+import type { CloudflarePlatformInfo } from '@marko/run-adapter-cloudflare';
+
+declare module "@marko/run" {
+ interface Platform extends CloudflarePlatformInfo {}
+
+ interface AppData extends Run.DefineApp<{
+ routes: {
+ "/": { verb: "get"; };
+ }
+ }> {}
+}
+
+declare module "../src/routes/+page.marko" {
+ namespace MarkoRun {
+ export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
+ export type Route = Run.Routes["/"];
+ export type Context = Run.MultiRouteContext & Marko.Global;
+ export type Handler = Run.HandlerLike;
+ export type GET = Run.HandlerLike;
+ export type HEAD = Run.HandlerLike;
+ export type POST = Run.HandlerLike;
+ export type PUT = Run.HandlerLike;
+ export type DELETE = Run.HandlerLike;
+ export type PATCH = Run.HandlerLike;
+ export type OPTIONS = Run.HandlerLike;
+ /** @deprecated use `((context, next) => { ... }) satisfies MarkoRun.Handler` instead */
+ export const route: Run.HandlerTypeFn;
+ }
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/__snapshots__/dev.expected.md b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/__snapshots__/dev.expected.md
new file mode 100644
index 00000000..1ec2ae91
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/__snapshots__/dev.expected.md
@@ -0,0 +1,35 @@
+# Loading
+
+```html
+
+```
+
+# Step 0
+ctx=>click(ctx)
+
+```diff
+- 0
++ 1
+
+```
+
+# Step 1
+ctx=>click(ctx)
+
+```diff
+- 1
++ 2
+
+```
+
+# Step 2
+ctx=>click(ctx)
+
+```diff
+- 2
++ 3
+
+```
+
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/package.json b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/package.json
new file mode 100644
index 00000000..ba6a3cc0
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "cloudflare-adapter-pages",
+ "version": "1.0.0",
+ "scripts": {}
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/components/counter.marko b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/components/counter.marko
new file mode 100644
index 00000000..436a9eb5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/components/counter.marko
@@ -0,0 +1,11 @@
+class {
+ declare state: { count: number };
+ onCreate() {
+ this.state = { count: 0 };
+ }
+ handleIncrement() {
+ this.state.count++;
+ }
+}
+
+
\ No newline at end of file
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/routes/+page.marko b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/routes/+page.marko
new file mode 100644
index 00000000..5dd8370a
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/src/routes/+page.marko
@@ -0,0 +1,12 @@
+
+
+
+
+ @marko/run Test Fixture
+
+
+
+
+
+
+
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/test.config.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/test.config.ts
new file mode 100644
index 00000000..d21d639a
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/test.config.ts
@@ -0,0 +1,13 @@
+import { Step, StepContext } from "../../main.test";
+
+export const steps: Step[] = [
+ (ctx) => click(ctx),
+ (ctx) => click(ctx),
+ (ctx) => click(ctx),
+];
+// Preview requires the Wrangler CLI, which isn't available in CI.
+export const skip_preview = true;
+
+async function click({ page }: StepContext) {
+ await page.click("button");
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/tsconfig.json b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/tsconfig.json
new file mode 100644
index 00000000..03ff88a5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig-base.json",
+ "include": ["src/**/*", ".marko-run/*"],
+}
\ No newline at end of file
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/vite.config.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/vite.config.ts
new file mode 100644
index 00000000..594176f8
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-pages/vite.config.ts
@@ -0,0 +1,10 @@
+import marko from "@marko/run/vite";
+import adapter from "@marko/run-adapter-cloudflare";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [marko({ adapter: adapter({ mode: "pages" }) })],
+ server: {
+ hmr: false,
+ },
+});
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.gitignore b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.gitignore
new file mode 100644
index 00000000..1521c8b7
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.gitignore
@@ -0,0 +1 @@
+dist
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.marko-run/routes.d.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.marko-run/routes.d.ts
new file mode 100644
index 00000000..fb83b4f5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/.marko-run/routes.d.ts
@@ -0,0 +1,36 @@
+/*
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
+ Do NOT manually edit this file or your changes will be lost.
+*/
+
+import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";
+import type * as Run from "@marko/run";
+import type { CloudflarePlatformInfo } from '@marko/run-adapter-cloudflare';
+
+declare module "@marko/run" {
+ interface Platform extends CloudflarePlatformInfo {}
+
+ interface AppData extends Run.DefineApp<{
+ routes: {
+ "/": { verb: "get"; };
+ }
+ }> {}
+}
+
+declare module "../src/routes/+page.marko" {
+ namespace MarkoRun {
+ export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
+ export type Route = Run.Routes["/"];
+ export type Context = Run.MultiRouteContext & Marko.Global;
+ export type Handler = Run.HandlerLike;
+ export type GET = Run.HandlerLike;
+ export type HEAD = Run.HandlerLike;
+ export type POST = Run.HandlerLike;
+ export type PUT = Run.HandlerLike;
+ export type DELETE = Run.HandlerLike;
+ export type PATCH = Run.HandlerLike;
+ export type OPTIONS = Run.HandlerLike;
+ /** @deprecated use `((context, next) => { ... }) satisfies MarkoRun.Handler` instead */
+ export const route: Run.HandlerTypeFn;
+ }
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/__snapshots__/dev.expected.md b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/__snapshots__/dev.expected.md
new file mode 100644
index 00000000..1ec2ae91
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/__snapshots__/dev.expected.md
@@ -0,0 +1,35 @@
+# Loading
+
+```html
+
+```
+
+# Step 0
+ctx=>click(ctx)
+
+```diff
+- 0
++ 1
+
+```
+
+# Step 1
+ctx=>click(ctx)
+
+```diff
+- 1
++ 2
+
+```
+
+# Step 2
+ctx=>click(ctx)
+
+```diff
+- 2
++ 3
+
+```
+
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/package.json b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/package.json
new file mode 100644
index 00000000..b4198c30
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "cloudflare-adapter-workers",
+ "version": "1.0.0",
+ "scripts": {}
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/components/counter.marko b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/components/counter.marko
new file mode 100644
index 00000000..436a9eb5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/components/counter.marko
@@ -0,0 +1,11 @@
+class {
+ declare state: { count: number };
+ onCreate() {
+ this.state = { count: 0 };
+ }
+ handleIncrement() {
+ this.state.count++;
+ }
+}
+
+
\ No newline at end of file
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/routes/+page.marko b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/routes/+page.marko
new file mode 100644
index 00000000..5dd8370a
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/src/routes/+page.marko
@@ -0,0 +1,12 @@
+
+
+
+
+ @marko/run Test Fixture
+
+
+
+
+
+
+
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/test.config.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/test.config.ts
new file mode 100644
index 00000000..d21d639a
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/test.config.ts
@@ -0,0 +1,13 @@
+import { Step, StepContext } from "../../main.test";
+
+export const steps: Step[] = [
+ (ctx) => click(ctx),
+ (ctx) => click(ctx),
+ (ctx) => click(ctx),
+];
+// Preview requires the Wrangler CLI, which isn't available in CI.
+export const skip_preview = true;
+
+async function click({ page }: StepContext) {
+ await page.click("button");
+}
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/tsconfig.json b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/tsconfig.json
new file mode 100644
index 00000000..03ff88a5
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig-base.json",
+ "include": ["src/**/*", ".marko-run/*"],
+}
\ No newline at end of file
diff --git a/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/vite.config.ts b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/vite.config.ts
new file mode 100644
index 00000000..604d276d
--- /dev/null
+++ b/packages/run/src/__tests__/fixtures/cloudflare-adapter-workers/vite.config.ts
@@ -0,0 +1,10 @@
+import marko from "@marko/run/vite";
+import adapter from "@marko/run-adapter-cloudflare";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [marko({ adapter: adapter() })],
+ server: {
+ hmr: false,
+ },
+});