1import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
2import * as Cloudflare from "@/Cloudflare/index.ts";
3import { readPrebuiltWorkerBundle } from "@/Cloudflare/Workers/Sources/Prebuilt";
4import * as Test from "@/Test/Alchemy";
5import { describe, expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as pathe from "pathe";
9import { expectUrlContains } from "../Utils/Http.ts";
10import { waitForWorkerToBeDeleted } from "../Utils/Worker.ts";
11
12const { test } = Test.make({ providers: Cloudflare.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19const main = pathe.resolve(import.meta.dirname, "fixtures/prebuilt/worker.mjs");
20
21describe.concurrent(
22 "Cloudflare.Worker with bundle: false",
23 { tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"] },
24 () => {
25 test.provider(
26 "uploads a prebuilt module graph byte-for-byte",
27 (stack) =>
28 Effect.gen(function* () {
29 const { accountId } = yield* yield* CloudflareEnvironment;
30
31 yield* stack.destroy();
32
33 // The bundle the provider must upload, computed from the source
34 // bytes on disk: entry first, additional modules named by their
35 // POSIX path relative to the entry's directory.
36 const expected = yield* readPrebuiltWorkerBundle({ main });
37 expect(expected.files.map((file) => file.path)).toEqual([
38 "worker.mjs",
39 "lib/format.mjs",
40 "lib/greeting.mjs",
41 "lib/notice.txt",
42 ]);
43
44 const worker = yield* stack.deploy(
45 Effect.gen(function* () {
46 return yield* Cloudflare.Worker("PrebuiltWorker", {
47 main,
48 bundle: false,
49 workersDev: true,
50 compatibility: {
51 date: "2024-01-01",
52 },
53 });
54 }),
55 );
56
57 // The stored bundle hash equals the hash of the source bytes
58 // only when no rolldown step ran — any re-bundling would
59 // minify the files and change every content hash.
60 expect(worker.hash?.bundle).toEqual(expected.hash);
61
62 // End-to-end: the response is assembled from values that flow
63 // through both nested ES modules and the nested text module, so
64 // it only renders if the module names survived the upload.
65 expect(worker.url).toBeDefined();
66 yield* expectUrlContains(
67 worker.url!,
68 "prebuilt-modules-survived alchemy-prebuilt-notice-4d2a!",
69 );
70
71 // Re-deploy with no changes: the prebuilt hash must be stable.
72 const redeployed = yield* stack.deploy(
73 Effect.gen(function* () {
74 return yield* Cloudflare.Worker("PrebuiltWorker", {
75 main,
76 bundle: false,
77 workersDev: true,
78 compatibility: {
79 date: "2024-01-01",
80 },
81 });
82 }),
83 );
84 expect(redeployed.hash?.bundle).toEqual(expected.hash);
85
86 yield* stack.destroy();
87 yield* waitForWorkerToBeDeleted(worker.workerName, accountId);
88 }).pipe(logLevel),
89 { timeout: 360_000 },
90 );
91 },
92);