1import * as Cloudflare from "@/Cloudflare/index.ts";
2import { describe, expect, it } from "alchemy-test";
3
4/**
5 * Compile-time pins for the framework Website resources' prop surfaces.
6 *
7 * The resources deliberately reject the Worker props their source dispatch
8 * owns (`script`, `bundle`, `source`, `vite`, `assets` where the framework
9 * owns assets) — passing one used to type-check and only fail at runtime
10 * inside `resolveSource`. These `@ts-expect-error` pins fail the build if
11 * an `Omit` is ever loosened again. The `() => {}` bodies never run; only
12 * the types matter.
13 */
14describe(
15 "Website prop surfaces",
16 {
17 tags: [
18 "unit",
19 "provider:cloudflare",
20 "provider:cloudflare:website",
21 "local",
22 ],
23 },
24 () => {
25 const _pins = [
26 () =>
27 Cloudflare.Website.Waku("W", {
28 // @ts-expect-error `script` is owned by the source dispatch
29 script: "export default {}",
30 }),
31 () =>
32 Cloudflare.Website.Waku("W", {
33 // @ts-expect-error `bundle` is owned by the source dispatch
34 bundle: false,
35 }),
36 () =>
37 Cloudflare.Website.SvelteKit("S", {
38 // @ts-expect-error `script` is owned by the source dispatch
39 script: "export default {}",
40 }),
41 () =>
42 Cloudflare.Website.SvelteKit("S", {
43 // @ts-expect-error `main` is not supported (no custom-entry seam)
44 main: "worker.ts",
45 }),
46 () =>
47 Cloudflare.Website.Nuxt("N", {
48 // @ts-expect-error `bundle` is owned by the source dispatch
49 bundle: false,
50 }),
51 () =>
52 Cloudflare.Website.Astro("A", {
53 // @ts-expect-error `main` is not supported (no custom-entry seam)
54 main: "worker.ts",
55 }),
56 () =>
57 Cloudflare.Website.Octane("O", {
58 // @ts-expect-error `main` is not supported (no custom-entry seam)
59 main: "worker.ts",
60 }),
61 () =>
62 Cloudflare.Website.Octane("O", {
63 // @ts-expect-error `script` is owned by the source dispatch
64 script: "export default {}",
65 }),
66 () =>
67 Cloudflare.Website.Astro("A", {
68 // @ts-expect-error `source` is owned by the resource itself
69 source: { provider: "x", options: {} },
70 }),
71 () =>
72 Cloudflare.Website.Astro("A", {
73 prerenderEnvironment: "node",
74 }),
75 () =>
76 Cloudflare.Website.Astro("A", {
77 prerenderEnvironment: "workerd",
78 }),
79 () =>
80 Cloudflare.Website.Astro("A", {
81 // @ts-expect-error only workerd and node are supported
82 prerenderEnvironment: "bun",
83 }),
84 () =>
85 Cloudflare.Website.Nextjs("X", {
86 // @ts-expect-error `script` is owned by the source dispatch
87 script: "export default {}",
88 }),
89 () =>
90 Cloudflare.Website.Nextjs("X", {
91 // @ts-expect-error `main` is not supported (OpenNext owns the entry)
92 main: "worker.ts",
93 }),
94 () =>
95 Cloudflare.Website.Nextjs("X", {
96 // @ts-expect-error `source` is owned by the resource itself
97 source: { provider: "x", options: {} },
98 }),
99 () =>
100 Cloudflare.Website.Foldkit("F", {
101 // @ts-expect-error `script` is owned by the source dispatch
102 script: "export default {}",
103 }),
104 () =>
105 Cloudflare.Website.Foldkit("F", {
106 // @ts-expect-error `bundle` is owned by the source dispatch
107 bundle: false,
108 }),
109 () =>
110 Cloudflare.Website.Foldkit("F", {
111 // @ts-expect-error `source` is owned by the resource itself
112 source: { provider: "x", options: {} },
113 }),
114 () =>
115 Cloudflare.Website.Foldkit("F", {
116 // @ts-expect-error `viteEnvironments` is not supported (Foldkit has no RSC split)
117 viteEnvironments: { entry: "rsc", children: ["ssr"] },
118 }),
119 // `main` IS supported — a Foldkit deployment may carry a custom Worker
120 // entry (API routes, error reporting, Durable Objects) alongside the
121 // client build. Pinned positively so an `Omit` can't quietly drop it.
122 () => Cloudflare.Website.Foldkit("F", { main: "src/worker.ts" }),
123 () =>
124 Cloudflare.Website.Vocs("Docs", {
125 // @ts-expect-error `source` is owned by the Vocs integration
126 source: { provider: "x", options: {} },
127 }),
128 () =>
129 Cloudflare.Website.Vocs("Docs", {
130 // @ts-expect-error Vocs owns its Waku/RSC worker entry
131 main: "worker.ts",
132 }),
133 // ── Flat-props doctrine pins ─────────────────────────────────────
134 // Framework-named config bags are dissolved into flat, explicitly
135 // typed props; the shared vocabulary (`outDir`, `spa`, `errorPage`)
136 // is identical across composites. These pins fail the build if a bag
137 // sneaks back in or a flat prop is dropped.
138 // Astro: framework config lives in `astro.config.*`; the `astro`
139 // prop is the deploy-time override bag merged OVER the file — an
140 // explicit serializable subset, no flat mirror props, no
141 // plugins/functions.
142 () =>
143 Cloudflare.Website.Astro("A", {
144 astro: {
145 site: "https://example.com",
146 base: "/docs",
147 output: "static",
148 srcDir: "./app",
149 publicDir: "./static",
150 outDir: "./dist",
151 trailingSlash: "always",
152 },
153 }),
154 () =>
155 Cloudflare.Website.Astro("A", {
156 config: "astro.prod.config.mjs",
157 }),
158 () =>
159 Cloudflare.Website.Astro("A", {
160 // @ts-expect-error `output` lives in the `astro` override bag
161 output: "static",
162 }),
163 () =>
164 Cloudflare.Website.Astro("A", {
165 // @ts-expect-error `site` belongs in astro.config.* or the `astro` bag
166 site: "https://example.com",
167 }),
168 () =>
169 Cloudflare.Website.Astro("A", {
170 astro: {
171 // @ts-expect-error no plugins in the serializable override bag
172 integrations: [],
173 },
174 }),
175 () =>
176 Cloudflare.Website.Astro("A", {
177 astro: {
178 // @ts-expect-error only "always" | "never" | "ignore"
179 trailingSlash: "sometimes",
180 },
181 }),
182 () =>
183 Cloudflare.Website.Nextjs("X", {
184 // @ts-expect-error the `nextjs` config bag is dissolved into flat props
185 nextjs: { devMode: "hmr" },
186 }),
187 () =>
188 Cloudflare.Website.Nextjs("X", {
189 // @ts-expect-error the OpenNext pipeline knobs are gone — configure
190 // them in open-next.config.ts; dev behavior moved to `dev.mode`
191 devMode: "hmr",
192 buildCommand: "npx next build",
193 minify: true,
194 skipNextBuild: false,
195 configPath: "open-next.config.ts",
196 debug: false,
197 }),
198 // The `nuxt` bag is the ONE sanctioned override surface: serializable
199 // deploy-time config merged over nuxt.config.ts (highest-priority c12
200 // layer). Pinned positively so an `Omit` can't quietly drop it.
201 () =>
202 Cloudflare.Website.Nuxt("N", {
203 nuxt: {
204 app: { baseURL: "/docs/" },
205 runtimeConfig: { public: { apiBase: "https://example.com" } },
206 },
207 }),
208 () =>
209 Cloudflare.Website.SvelteKit("S", {
210 // @ts-expect-error `adapter` removed — fallback generation is derived
211 // from the platform-native `assets.notFoundHandling` knob
212 adapter: { notFoundHandling: "404-page", fallback: "spa" },
213 }),
214 // The `kit` bag is the deploy-time override seam: JSON-serializable
215 // config merged over the user's own `sveltekit(...)` call (the prop
216 // wins). Pinned positively so an `Omit` can't quietly drop it.
217 () =>
218 Cloudflare.Website.SvelteKit("S", {
219 kit: { paths: { base: "/docs" } },
220 }),
221 () =>
222 Cloudflare.Website.SvelteKit("S", {
223 assets: { notFoundHandling: "404-page" },
224 }),
225 () =>
226 Cloudflare.Website.Waku("W", {
227 // @ts-expect-error framework config lives in the `waku` override bag
228 srcDir: "app",
229 }),
230 () =>
231 Cloudflare.Website.Waku("W", {
232 // @ts-expect-error framework config lives in the `waku` override bag
233 outDir: "build",
234 }),
235 // The `waku` bag is the deploy-time override seam, merged over the
236 // project's own `waku.config.*` (per-key; deploy-time values win).
237 () =>
238 Cloudflare.Website.Waku("W", {
239 waku: { srcDir: "app", distDir: "build", basePath: "/docs/" },
240 }),
241 () =>
242 Cloudflare.Website.Vite("V", {
243 // No spa/errorPage sugar on CF Vite: Workers Assets' own
244 // notFoundHandling is the platform-native surface.
245 assets: { notFoundHandling: "single-page-application" },
246 }),
247 () =>
248 Cloudflare.Website.Vite("V", {
249 // @ts-expect-error spa sugar deliberately not offered on CF Vite
250 spa: true,
251 }),
252 // ── CF Vite/Foldkit: config belongs in vite.config.* ─────────────
253 // The CF composites build through the Cloudflare Vite plugin, whose
254 // pipeline has no inline-override or configFile seam — so unlike the
255 // AWS composites there is no `vite` override bag and no `config`
256 // prop; `base`/`outDir` live only in the project's own config file.
257 () =>
258 Cloudflare.Website.Vite("V", {
259 // @ts-expect-error no top-level `base` — set it in vite.config.*
260 base: "/docs/",
261 }),
262 () =>
263 Cloudflare.Website.Vite("V", {
264 // @ts-expect-error no top-level `outDir` — the plugin owns the output layout
265 outDir: "dist",
266 }),
267 () =>
268 Cloudflare.Website.Vite("V", {
269 // @ts-expect-error no `vite` override bag on CF (no seam through the plugin pipeline)
270 vite: { base: "/docs/" },
271 }),
272 () =>
273 Cloudflare.Website.Foldkit("F", {
274 // @ts-expect-error no top-level `base` — set it in vite.config.*
275 base: "/app/",
276 }),
277 () =>
278 Cloudflare.Website.Foldkit("F", {
279 // @ts-expect-error no `vite` override bag on CF (no seam through the plugin pipeline)
280 vite: { base: "/app/" },
281 }),
282 // spa/errorPage sugar was dropped from CF Foldkit with CF Vite's:
283 // Workers Assets' notFoundHandling (defaulted to SPA for Foldkit) is
284 // the platform-native surface.
285 () =>
286 Cloudflare.Website.Foldkit("F", {
287 // @ts-expect-error spa sugar removed — use assets.notFoundHandling
288 spa: false,
289 }),
290 () =>
291 Cloudflare.Website.Foldkit("F", {
292 // @ts-expect-error errorPage sugar removed — use assets.notFoundHandling: "404-page"
293 errorPage: "404.html",
294 }),
295 ];
296
297 it("rejects source-dispatch props at the type level", () => {
298 // The pins above are compile-time only.
299 expect(_pins.length).toBeGreaterThan(0);
300 });
301 },
302);