| 1 | import * as Cloudflare from "@/Cloudflare/index.ts"; |
| 2 | import * as Test from "@/Test/Alchemy"; |
| 3 | import { expect } from "alchemy-test"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Schedule from "effect/Schedule"; |
| 6 | import * as HttpClient from "effect/http/HttpClient"; |
| 7 | import * as pathe from "pathe"; |
| 8 | import Stack from "./fixtures/url-stack.ts"; |
| 9 | |
| 10 | const { test, beforeAll, afterAll, deploy, destroy } = Test.make({ |
| 11 | providers: Cloudflare.providers(), |
| 12 | }); |
| 13 | |
| 14 | const stack = beforeAll(deploy(Stack)); |
| 15 | afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack)); |
| 16 | |
| 17 | // Fresh workers.dev URLs can briefly serve Cloudflare's placeholder page with |
| 18 | // a 200, so retry until the body parses as JSON (the placeholder is HTML). |
| 19 | const getJson = (url: string) => |
| 20 | HttpClient.HttpClient.pipe( |
| 21 | Effect.flatMap((client) => client.get(url)), |
| 22 | Effect.flatMap((res) => res.json), |
| 23 | Effect.retry({ |
| 24 | schedule: Schedule.exponential("1 second"), |
| 25 | times: 12, |
| 26 | }), |
| 27 | Effect.map((body) => body as Record<string, string>), |
| 28 | ); |
| 29 | |
| 30 | test( |
| 31 | "effectful worker reads its own URL via yield* Worker.URL", |
| 32 | Effect.gen(function* () { |
| 33 | const { effectUrl } = yield* stack; |
| 34 | const body = yield* getJson(`${effectUrl}/`); |
| 35 | expect(body.url).toBe(effectUrl); |
| 36 | }), |
| 37 | { |
| 38 | tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"], |
| 39 | timeout: 180_000, |
| 40 | }, |
| 41 | ); |
| 42 | |
| 43 | test( |
| 44 | "async worker receives its own URL via env: { PUBLIC_URL: Worker.URL }", |
| 45 | Effect.gen(function* () { |
| 46 | const { asyncUrl } = yield* stack; |
| 47 | const body = yield* getJson(`${asyncUrl}/`); |
| 48 | expect(body.url).toBe(asyncUrl); |
| 49 | }), |
| 50 | { |
| 51 | tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"], |
| 52 | timeout: 180_000, |
| 53 | }, |
| 54 | ); |
| 55 | |
| 56 | // The canonical VITE_PUBLIC_URL use case: the URL is resolved before the vite |
| 57 | // build, so it reaches the bundle two ways — inlined into `import.meta.env` |
| 58 | // via `define` at build time, and as a plain_text env binding at deploy time. |
| 59 | test.provider.skipIf(!!process.env.FAST)( |
| 60 | "vite worker inlines VITE_PUBLIC_URL at build time and binds it at runtime", |
| 61 | (stack) => |
| 62 | Effect.gen(function* () { |
| 63 | yield* stack.destroy(); |
| 64 | |
| 65 | const rootDir = pathe.resolve( |
| 66 | import.meta.dirname, |
| 67 | "fixtures/vite-url-fixture", |
| 68 | ); |
| 69 | const site = yield* stack.deploy( |
| 70 | Effect.gen(function* () { |
| 71 | return yield* Cloudflare.Worker("UrlViteWorker", { |
| 72 | vite: { |
| 73 | rootDir, |
| 74 | memo: { |
| 75 | include: [ |
| 76 | "index.html", |
| 77 | "package.json", |
| 78 | "vite.config.ts", |
| 79 | "src/**", |
| 80 | ], |
| 81 | }, |
| 82 | }, |
| 83 | compatibility: { |
| 84 | date: "2024-09-23", |
| 85 | flags: ["nodejs_compat"], |
| 86 | }, |
| 87 | env: { |
| 88 | VITE_PUBLIC_URL: Cloudflare.Worker.URL, |
| 89 | }, |
| 90 | }); |
| 91 | }), |
| 92 | ); |
| 93 | |
| 94 | expect(site.url).toBeDefined(); |
| 95 | const body = yield* getJson(`${site.url!}/self-url`); |
| 96 | expect(body.inlined).toBe(site.url); |
| 97 | expect(body.env).toBe(site.url); |
| 98 | |
| 99 | yield* stack.destroy(); |
| 100 | }), |
| 101 | { |
| 102 | tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"], |
| 103 | timeout: 360_000, |
| 104 | }, |
| 105 | ); |