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