1import * as AWS from "@/AWS";
2import * as Test from "@/Test/Alchemy";
3import { describe, expect } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7import * as pathe from "pathe";
8import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
9import { expectUrlContains, expectUrlOk } from "../../Cloudflare/Utils/Http.ts";
10
11// `dev: true` runs local providers behind the RPC sidecar proxy by default,
12// matching the process topology of the real `alchemy dev` command.
13const { test } = Test.make({ providers: AWS.providers(), dev: true });
14
15const fixtureDir = pathe.resolve(
16 import.meta.dirname,
17 "fixtures",
18 "react-router-app",
19);
20
21// Clone under the alchemy package so `@react-router/dev`, `react-router`,
22// `@react-router/node`, `isbot`, `react`, and `vite` resolve from the
23// workspace's node_modules (the fixture has none of its own).
24const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
25
26const fixtureEntries = [
27 ".gitignore",
28 "package.json",
29 "react-router.config.ts",
30 "vite.config.ts",
31 "app",
32 "public",
33];
34
35describe(
36 "AWS.Website.ReactRouter local",
37 { tags: ["provider:aws", "provider:aws:website", "local"] },
38 () => {
39 test.provider(
40 "dev runs React Router's own Vite dev server with no cloud resources",
41 (stack) =>
42 Effect.gen(function* () {
43 yield* stack.destroy();
44
45 const rootDir = yield* cloneFixture(fixtureDir, {
46 prefix: "alchemy-react-router-aws-local-",
47 tempRoot,
48 entries: fixtureEntries,
49 });
50
51 const deployed = yield* stack.deploy(
52 Effect.gen(function* () {
53 const site = yield* AWS.Website.ReactRouter("ReactRouterSite", {
54 rootDir,
55 env: {
56 REACT_ROUTER_ENV_MARKER: "react-router-aws-dev-env-marker",
57 },
58 });
59 return { site };
60 }),
61 );
62
63 // The site is the framework's own dev server: a localhost URL and no
64 // cloud rows at all (proof no AWS call ran).
65 const url = deployed.site.url! as string;
66 expect(url).toMatch(
67 /^http:\/\/(localhost|127\.0\.0\.1|\[[0-9a-fA-F:]+\])/,
68 );
69 // The URL is an origin, not a directory — appending a path must not
70 // produce a double slash.
71 expect(url.endsWith("/")).toBe(false);
72 expect(deployed.site.distribution).toBeUndefined();
73 expect(deployed.site.server).toBeUndefined();
74 expect(deployed.site.bucket).toBeUndefined();
75
76 // The dev server is reachable from another process.
77 expect(yield* expectUrlOk(`${url}/`)).toBe(200);
78
79 yield* stack.destroy();
80 }),
81 { timeout: 600_000 },
82 );
83
84 test.provider(
85 "dev serves SSR, the project's vite.config.ts, injected env, and HMR",
86 (stack) =>
87 Effect.gen(function* () {
88 const fs = yield* FileSystem.FileSystem;
89 const path = yield* Path.Path;
90
91 yield* stack.destroy();
92
93 const rootDir = yield* cloneFixture(fixtureDir, {
94 prefix: "alchemy-react-router-aws-local-ssr-",
95 tempRoot,
96 entries: fixtureEntries,
97 });
98
99 const deployed = yield* stack.deploy(
100 Effect.gen(function* () {
101 const site = yield* AWS.Website.ReactRouter("ReactRouterSite", {
102 rootDir,
103 env: {
104 REACT_ROUTER_ENV_MARKER: "react-router-aws-dev-env-marker",
105 },
106 });
107 return { site };
108 }),
109 );
110
111 const url = deployed.site.url! as string;
112
113 // SSR page served by the React Router dev server (native HMR).
114 yield* expectUrlContains(`${url}/`, "REACT_ROUTER_AWS_PAGE_MARKER", {
115 timeout: "120 seconds",
116 label: "dev SSR home page",
117 });
118 // The fixture's own vite.config.ts applied in dev too.
119 yield* expectUrlContains(
120 `${url}/`,
121 "config:react-router-aws-user-config-loaded",
122 { label: "user vite.config.ts applied (dev)" },
123 );
124 // server.environment reaches the dev server's process env — the same
125 // values the Lambda gets on deploy (dev/live parity).
126 yield* expectUrlContains(
127 `${url}/`,
128 "env:react-router-aws-dev-env-marker",
129 { label: "server.environment injected into dev server" },
130 );
131 // Resource route through the dev server.
132 yield* expectUrlContains(
133 `${url}/api/hello?echo=dev`,
134 "REACT_ROUTER_AWS_API_MARKER",
135 { label: "resource route (dev)" },
136 );
137
138 // ── HMR: edit the resource route in place. The stack is NOT
139 // re-applied — vite's dev rebuild must pick the change up and serve
140 // it through the same URL ─────────────────────────────────────────
141 const helloPath = path.join(rootDir, "app/routes/api.hello.ts");
142 const hello = yield* fs.readFileString(helloPath);
143 yield* fs.writeFileString(
144 helloPath,
145 hello.replace(
146 "REACT_ROUTER_AWS_API_MARKER",
147 "REACT_ROUTER_AWS_API_MARKER_V2",
148 ),
149 );
150 yield* expectUrlContains(
151 `${url}/api/hello?echo=dev`,
152 "REACT_ROUTER_AWS_API_MARKER_V2",
153 { timeout: "90 seconds", label: "resource route after HMR edit" },
154 );
155
156 yield* stack.destroy();
157 }),
158 { timeout: 600_000 },
159 );
160 },
161);