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