| 1 | import { |
| 2 | getCompatibility, |
| 3 | getToolingCompatibility, |
| 4 | } from "@/Cloudflare/Workers/Compatibility"; |
| 5 | import type { WorkerProps } from "@/Cloudflare/Workers/Worker"; |
| 6 | import * as Output from "@/Output"; |
| 7 | import { describe, expect, test } from "alchemy-test"; |
| 8 | |
| 9 | describe( |
| 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 | |
| 192 | describe( |
| 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 | ); |