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 Logo + +
+ @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, + }, +});