1import {
2 getCompatibility,
3 getToolingCompatibility,
4} from "@/Cloudflare/Workers/Compatibility";
5import type { WorkerProps } from "@/Cloudflare/Workers/Worker";
6import * as Output from "@/Output";
7import { describe, expect, test } from "alchemy-test";
8
9describe(
10 "getCompatibility",
11 {
12 tags: [
13 "unit",
14 "provider:cloudflare",
15 "provider:cloudflare:worker",
16 "local",
17 ],
18 },
19 () => {
20 for (const date of ["2024-09-23", "2026-08-31"]) {
21 for (const flags of [[], ["nodejs_als", "legacy_module_registry"]]) {
22 test(`preserves explicit prebuilt flags ${JSON.stringify(flags)} on ${date}`, () => {
23 expect(
24 getCompatibility({
25 isExternal: true,
26 bundle: false,
27 main: "./dist/worker.js",
28 compatibility: { date, flags },
29 } as WorkerProps),
30 ).toEqual({ date, flags });
31 });
32 }
33
34 test(`retains defaults for prebuilt Workers with omitted flags on ${date}`, () => {
35 const { flags } = getCompatibility({
36 isExternal: true,
37 bundle: false,
38 compatibility: { date },
39 } as WorkerProps);
40 expect(flags).toEqual(
41 date === "2024-09-23"
42 ? ["new_module_registry", "nodejs_compat"]
43 : ["new_module_registry"],
44 );
45 });
46 }
47
48 test("retains defaults for bundled external Workers with explicit empty flags", () => {
49 for (const bundle of [undefined, true]) {
50 const { flags } = getCompatibility({
51 isExternal: true,
52 bundle,
53 compatibility: { date: "2024-09-23", flags: [] },
54 } as WorkerProps);
55 expect(flags).toEqual(["new_module_registry", "nodejs_compat"]);
56 }
57 });
58
59 test("retains Effect runtime requirements with explicit empty flags", () => {
60 const { flags } = getCompatibility({
61 isExternal: false,
62 bundle: false,
63 compatibility: { date: "2024-09-23", flags: [] },
64 } as WorkerProps);
65 expect(flags).toEqual([
66 "new_module_registry",
67 "nodejs_compat",
68 "handle_cross_request_promise_resolution",
69 ]);
70 });
71
72 test("defaults both Effect and external workers to the new module registry", () => {
73 for (const isExternal of [false, true]) {
74 const { flags } = getCompatibility({ isExternal } as WorkerProps);
75 expect(flags).toContain("new_module_registry");
76 }
77 });
78
79 test("respects the legacy module registry opt-out", () => {
80 const { flags } = getCompatibility({
81 compatibility: { flags: ["legacy_module_registry"] },
82 } as WorkerProps);
83 expect(flags).toContain("legacy_module_registry");
84 expect(flags).not.toContain("new_module_registry");
85 });
86
87 test("does not duplicate an explicit new module registry flag", () => {
88 const { flags } = getCompatibility({
89 compatibility: { flags: ["new_module_registry"] },
90 } as WorkerProps);
91 expect(
92 flags.filter((flag) => flag === "new_module_registry"),
93 ).toHaveLength(1);
94 });
95
96 // Cloudflare enables both Node.js compatibility modes by date from
97 // 2026-08-04, so Alchemy's newer default no longer emits a redundant flag.
98 test("uses date-default Node.js compatibility for Effect-native workers", () => {
99 const { date, flags } = getCompatibility({} as WorkerProps);
100 expect(date).toBe("2026-08-31");
101 expect(flags).not.toContain("nodejs_compat");
102 });
103
104 test("uses date-default Node.js compatibility for external workers", () => {
105 const { flags } = getCompatibility({ isExternal: true } as WorkerProps);
106 expect(flags).not.toContain("nodejs_compat");
107 });
108
109 test("does not duplicate an explicit nodejs_compat", () => {
110 const { flags } = getCompatibility({
111 isExternal: true,
112 compatibility: { flags: ["nodejs_compat"] },
113 } as WorkerProps);
114 expect(flags.filter((f) => f === "nodejs_compat")).toHaveLength(1);
115 });
116
117 // nodejs_compat with a date before 2024-09-23 selects the legacy v1 mode,
118 // which lacks `process.getBuiltinModule` — the build-side unenv transform
119 // emits code that needs it, so the deploy would fail at script startup.
120 test("skips the external-worker default for pre-v2 compatibility dates", () => {
121 const { flags } = getCompatibility({
122 isExternal: true,
123 compatibility: { date: "2024-01-01" },
124 } as WorkerProps);
125 expect(flags).not.toContain("nodejs_compat");
126 });
127
128 test("applies the external-worker default from the v2 date onward", () => {
129 const { flags } = getCompatibility({
130 isExternal: true,
131 compatibility: { date: "2024-09-23" },
132 } as WorkerProps);
133 expect(flags).toContain("nodejs_compat");
134 });
135
136 // An explicit no_nodejs_compat opts out — appending nodejs_compat
137 // alongside it would send Cloudflare a contradictory flag pair.
138 test("no_nodejs_compat opts out of the default", () => {
139 const { flags } = getCompatibility({
140 isExternal: true,
141 compatibility: { flags: ["no_nodejs_compat"] },
142 } as WorkerProps);
143 expect(flags).toContain("no_nodejs_compat");
144 expect(flags).not.toContain("nodejs_compat");
145 });
146
147 // Python Workers don't go through the JS bundler: they get the beta
148 // python_workers flag and no nodejs_compat default.
149 test("python workers get python_workers and no nodejs_compat default", () => {
150 const { flags } = getCompatibility({
151 isExternal: true,
152 main: "./src/entry.py",
153 } as WorkerProps);
154 expect(flags).toContain("python_workers");
155 expect(flags).not.toContain("new_module_registry");
156 expect(flags).not.toContain("nodejs_compat");
157 });
158
159 // `precreate` is handed raw, *unresolved* props so resources in a
160 // dependency cycle can signal early, and `main` is an Input — a `main`
161 // derived from another resource's output (e.g. a `Command.Build` outdir)
162 // is still an Output there. An unresolved Output is a callable Proxy, so
163 // a `main !== undefined` guard passes and `main.split("?")[0]` used to
164 // blow up with a bare `TypeError` out of the Worker's pre-create.
165 test("tolerates an unresolved Output main", () => {
166 const main = Output.map(
167 Output.literal("/build"),
168 (dir: string) => `${dir}/server/entry.mjs`,
169 ) as unknown as string;
170 const { flags } = getCompatibility({
171 isExternal: true,
172 main,
173 } as WorkerProps);
174 expect(flags).not.toContain("python_workers");
175 expect(flags).not.toContain("nodejs_compat");
176 });
177
178 test("forces handle_cross_request_promise_resolution for Effect workers on old dates", () => {
179 const { flags } = getCompatibility({
180 compatibility: { date: "2024-10-01" },
181 } as WorkerProps);
182 expect(flags).toContain("handle_cross_request_promise_resolution");
183 });
184
185 test("omits handle_cross_request_promise_resolution once default-on", () => {
186 const { flags } = getCompatibility({} as WorkerProps);
187 expect(flags).not.toContain("handle_cross_request_promise_resolution");
188 });
189 },
190);
191
192describe(
193 "getToolingCompatibility",
194 {
195 tags: [
196 "unit",
197 "provider:cloudflare",
198 "provider:cloudflare:worker",
199 "local",
200 ],
201 },
202 () => {
203 test("materializes date-default nodejs_compat for downstream tools", () => {
204 expect(
205 getToolingCompatibility({ date: "2026-08-31", flags: [] }, "worker.ts")
206 .flags,
207 ).toEqual(["nodejs_compat"]);
208 });
209
210 test("preserves an explicit opt-out", () => {
211 expect(
212 getToolingCompatibility(
213 { date: "2026-08-31", flags: ["no_nodejs_compat"] },
214 "worker.ts",
215 ).flags,
216 ).toEqual(["no_nodejs_compat"]);
217 });
218
219 test("does not add Node compatibility to Python tooling", () => {
220 expect(
221 getToolingCompatibility(
222 { date: "2026-08-31", flags: ["python_workers"] },
223 "worker.py",
224 ).flags,
225 ).toEqual(["python_workers"]);
226 });
227 },
228);