1/**
2 * Repro for the workers.dev URL regression under `alchemy dev`:
3 * a Worker previously deployed live (stamped `providerMode: "live"`) is
4 * replaced live → local by a dev run. The stack output referencing
5 * `worker.url` must resolve to the LOCAL dev server URL (localhost), not
6 * the deployed workers.dev / custom-domain URL.
7 */
8import * as Cloudflare from "@/Cloudflare/index.ts";
9import { Stack } from "@/Stack";
10import { State, type ResourceState } from "@/State";
11import * as Test from "@/Test/Alchemy";
12import { expect } from "alchemy-test";
13import * as Effect from "effect/Effect";
14import { inDev } from "../../test.resources.ts";
15
16const { test } = Test.make({ providers: Cloudflare.providers() });
17
18const script = `export default {
19 async fetch() {
20 return Response.json({ ok: true });
21 },
22};`;
23
24test.provider(
25 "worker.url resolves to localhost after a live → local mode switch",
26 (stack) =>
27 Effect.gen(function* () {
28 yield* stack.destroy();
29
30 const program = Effect.gen(function* () {
31 const worker = yield* Cloudflare.Worker("url-mode-switch-worker", {
32 script,
33 });
34 return { url: worker.url, urls: worker.urls };
35 });
36
37 // 1. live deploy: url is the workers.dev URL, row stamped live.
38 const live = yield* stack.deploy(program);
39 expect(String(live.url)).toContain("workers.dev");
40
41 // 2. dev run over the same stage: live → local replacement. The
42 // stack output must now resolve to the local dev server URL.
43 const dev = yield* inDev(stack.deploy(program));
44 expect(String(dev.url)).toContain("localhost");
45
46 yield* stack.destroy();
47 }),
48 {
49 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
50 timeout: 240_000,
51 },
52);
53
54/**
55 * A state row written before provider modes existed (pre-beta.66) has no
56 * `providerMode` stamp. Such a row was necessarily deployed LIVE — the old
57 * engine had no local mode. A dev run must therefore treat it as live and
58 * plan the live → local replacement; assuming it is "already local" leaves
59 * the deployed cloud attrs (workers.dev / custom-domain URL) masquerading
60 * as the local instance and never starts a dev server.
61 */
62test.provider(
63 "a legacy (unstamped) live row is replaced by a dev run, not assumed local",
64 (stack) =>
65 Effect.gen(function* () {
66 yield* stack.destroy();
67
68 const program = Effect.gen(function* () {
69 const worker = yield* Cloudflare.Worker("url-legacy-row-worker", {
70 script,
71 });
72 return { url: worker.url, urls: worker.urls };
73 });
74
75 // 1. live deploy, then simulate a pre-providerMode row.
76 const live = yield* stack.deploy(program);
77 expect(String(live.url)).toContain("workers.dev");
78
79 const fqn = "url-legacy-row-worker";
80 const state = yield* yield* State;
81 const stk = yield* Stack;
82 const row = (yield* state.get({
83 stack: stk.name,
84 stage: stk.stage,
85 fqn,
86 })) as ResourceState;
87 yield* state.set({
88 stack: stk.name,
89 stage: stk.stage,
90 fqn,
91 value: { ...row, providerMode: undefined },
92 });
93
94 // 2. dev run: the worker must come up locally.
95 const dev = yield* inDev(stack.deploy(program));
96 expect(String(dev.url)).toContain("localhost");
97
98 yield* stack.destroy();
99 }),
100 {
101 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
102 timeout: 240_000,
103 },
104);
105
106const zoneName =
107 process.env.CLOUDFLARE_TEST_DNS_ZONE_NAME ?? "alchemy-test-2.us";
108
109test.provider(
110 "worker.url with a custom domain resolves to localhost after live → local",
111 (stack) =>
112 Effect.gen(function* () {
113 yield* stack.destroy();
114
115 const hostname = `url-mode-switch.${zoneName}`;
116 const program = Effect.gen(function* () {
117 const worker = yield* Cloudflare.Worker("url-mode-switch-domain", {
118 script,
119 domain: hostname,
120 });
121 return { url: worker.url, urls: worker.urls };
122 });
123
124 // 1. live deploy: url is the custom domain, row stamped live.
125 const live = yield* stack.deploy(program);
126 expect(String(live.url)).toBe(`https://${hostname}`);
127
128 // 2. dev run: live → local replacement. The output must resolve to
129 // the local dev server URL, not the deployed custom domain.
130 const dev = yield* inDev(stack.deploy(program));
131 expect(String(dev.url)).toContain("localhost");
132
133 yield* stack.destroy();
134 }),
135 {
136 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
137 timeout: 240_000,
138 },
139);