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 } 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(import.meta.dirname, "fixtures", "waku-app");
16
17// Clone under the alchemy package so `waku`/`react` resolve from the
18// workspace's hoisted node_modules (the fixture has no node_modules).
19const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
20
21const fixtureEntries = [
22 ".gitignore",
23 "package.json",
24 "tsconfig.json",
25 "src",
26 "public",
27];
28
29describe(
30 "AWS.Website.Waku local",
31 { tags: ["provider:aws", "provider:aws:website", "local"] },
32 () => {
33 test.provider(
34 "dev runs Waku's own dev server with no cloud resources",
35 (stack) =>
36 Effect.gen(function* () {
37 const fs = yield* FileSystem.FileSystem;
38 const path = yield* Path.Path;
39
40 yield* stack.destroy();
41
42 const rootDir = yield* cloneFixture(fixtureDir, {
43 prefix: "alchemy-waku-aws-local-",
44 tempRoot,
45 entries: fixtureEntries,
46 });
47
48 const deployed = yield* stack.deploy(
49 Effect.gen(function* () {
50 const site = yield* AWS.Website.Waku("WakuSite", {
51 rootDir,
52 });
53 return { site };
54 }),
55 );
56
57 // The site is the framework's own dev server: a localhost URL and
58 // no cloud rows at all (proof no AWS call ran).
59 const url = deployed.site.url! as string;
60 expect(url).toMatch(
61 /^http:\/\/(localhost|127\.0\.0\.1|\[[0-9a-fA-F:]+\])/,
62 );
63 expect(deployed.site.distribution).toBeUndefined();
64 expect(deployed.site.server).toBeUndefined();
65 expect(deployed.site.bucket).toBeUndefined();
66
67 // SSR page served by the waku dev server (native HMR toolchain).
68 yield* expectUrlContains(`${url}/`, "WAKU_AWS_PAGE_MARKER", {
69 timeout: "120 seconds",
70 label: "dev SSR home page",
71 });
72 // API route (waku's `_api` pattern) through the dev server.
73 yield* expectUrlContains(
74 `${url}/echo?echo=dev`,
75 "WAKU_AWS_API_MARKER",
76 {
77 label: "API route (dev)",
78 },
79 );
80 yield* expectUrlContains(`${url}/echo?echo=dev`, "dev", {
81 label: "API route query echo (dev)",
82 });
83
84 // ── HMR: edit the API route in place. The stack is NOT re-applied —
85 // waku's vite dev rebuild must pick the change up and serve it
86 // through the same URL ───────────────────────────────────────────
87 const echoPath = path.join(rootDir, "src/pages/_api/echo.ts");
88 const echo = yield* fs.readFileString(echoPath);
89 yield* fs.writeFileString(
90 echoPath,
91 echo.replaceAll("WAKU_AWS_API_MARKER", "WAKU_AWS_API_MARKER_V2"),
92 );
93 yield* expectUrlContains(
94 `${url}/echo?echo=dev`,
95 "WAKU_AWS_API_MARKER_V2",
96 { timeout: "90 seconds", label: "API route after HMR edit" },
97 );
98 // The route still round-trips its query after the reload.
99 yield* expectUrlContains(`${url}/echo?echo=post-hmr`, "post-hmr", {
100 label: "API route query echo after HMR edit",
101 });
102
103 yield* stack.destroy();
104 }),
105 { timeout: 600_000 },
106 );
107 },
108);