1import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
2import * as Cloudflare from "@/Cloudflare/index.ts";
3import * as Test from "@/Test/Alchemy";
4import { describe, expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import { MinimumLogLevel } from "effect/References";
9import * as pathe from "pathe";
10import { cloneFixture } from "../Utils/Fixture.ts";
11import { expectUrlContains } from "../Utils/Http.ts";
12import {
13 expectWorkerExists,
14 waitForWorkerToBeDeleted,
15} from "../Utils/Worker.ts";
16
17const { test } = Test.make({ providers: Cloudflare.providers() });
18
19const logLevel = Effect.provideService(
20 MinimumLogLevel,
21 process.env.DEBUG ? "Debug" : "Info",
22);
23
24const fixtureDir = pathe.resolve(
25 import.meta.dirname,
26 "../../../../../examples/cloudflare-website-vocs",
27);
28const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
29const fixtureEntries = [
30 "package.json",
31 "public",
32 "src",
33 "tsconfig.json",
34 "vocs.config.ts",
35];
36
37const vocsProps = (rootDir: string) => ({
38 rootDir,
39 workersDev: { enabled: true, previewsEnabled: true },
40 compatibility: { date: "2026-03-10" },
41 memo: {
42 include: [
43 "src/**",
44 "public/**",
45 "package.json",
46 "tsconfig.json",
47 "vocs.config.ts",
48 ],
49 },
50});
51
52describe.concurrent(
53 "Vocs",
54 {
55 tags: [
56 "provider:cloudflare",
57 "provider:cloudflare:website",
58 "provider:cloudflare:worker",
59 "live",
60 ],
61 },
62 () => {
63 test.provider(
64 "Vocs: deploys SSR, MDX, generated and public assets, and memoizes rebuilds",
65 (stack) =>
66 Effect.gen(function* () {
67 const { accountId } = yield* yield* CloudflareEnvironment;
68 const fs = yield* FileSystem.FileSystem;
69 const path = yield* Path.Path;
70
71 yield* stack.destroy();
72
73 const rootDir = yield* cloneFixture(fixtureDir, {
74 prefix: "alchemy-vocs-",
75 tempRoot,
76 entries: fixtureEntries,
77 });
78
79 const deploy = () =>
80 stack.deploy(
81 Cloudflare.Website.Vocs("VocsSite", vocsProps(rootDir)),
82 );
83
84 const site1 = yield* deploy();
85
86 expect(site1.url).toBeDefined();
87 expect(site1.hash?.input).toBeDefined();
88 yield* expectWorkerExists(site1.workerName, accountId);
89
90 yield* expectUrlContains(`${site1.url!}/`, "Alchemy with Vocs", {
91 timeout: "120 seconds",
92 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
93 label: "Vocs SSR shell and home page",
94 });
95 yield* expectUrlContains(`${site1.url!}/guide`, "Deployment guide", {
96 timeout: "60 seconds",
97 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
98 label: "Vocs prerendered MDX guide",
99 });
100 yield* expectUrlContains(
101 `${site1.url!}/counter`,
102 "Interactive component",
103 {
104 timeout: "60 seconds",
105 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
106 label: "Vocs MDX client-component page",
107 },
108 );
109 yield* expectUrlContains(
110 `${site1.url!}/hello.txt`,
111 "hello from the Vocs public directory",
112 {
113 timeout: "60 seconds",
114 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
115 label: "Vocs public asset",
116 },
117 );
118 yield* expectUrlContains(
119 `${site1.url!}/llms.txt`,
120 "Alchemy with Vocs",
121 {
122 timeout: "60 seconds",
123 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
124 label: "Vocs generated llms asset",
125 },
126 );
127
128 const site2 = yield* deploy();
129 expect(site2.hash?.input).toEqual(site1.hash?.input);
130 expect(site2.url).toBe(site1.url);
131
132 const pagePath = path.join(rootDir, "src/pages/index.mdx");
133 const page = yield* fs.readFileString(pagePath);
134 yield* fs.writeFileString(
135 pagePath,
136 page.replace(
137 "No Wrangler configuration or Vocs adapter setup is required.",
138 "This Vocs page was updated by a deployment.",
139 ),
140 );
141
142 const site3 = yield* deploy();
143 expect(site3.hash?.input).not.toEqual(site1.hash?.input);
144 yield* expectUrlContains(
145 `${site3.url!}/?__alchemy_cb=${Date.now()}`,
146 "This Vocs page was updated by a deployment.",
147 {
148 timeout: "180 seconds",
149 headers: { accept: "text/html", "user-agent": "Mozilla/5.0" },
150 label: "Vocs page after source update",
151 },
152 );
153
154 yield* stack.destroy();
155 yield* waitForWorkerToBeDeleted(site1.workerName, accountId);
156 }).pipe(logLevel),
157 { timeout: 360_000 },
158 );
159 },
160);