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", "vite-app");
16
17// Clone under the alchemy package so `vite` resolves from the workspace's
18// 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 "index.html",
25 "src",
26 "public",
27];
28
29describe(
30 "AWS.Website.Vite local",
31 { tags: ["provider:aws", "provider:aws:website", "local"] },
32 () => {
33 test.provider(
34 "dev runs Vite'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-vite-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.Vite("ViteSite", {
51 rootDir,
52 });
53 return { site };
54 }),
55 );
56
57 // The site is Vite's own dev server: a localhost URL and no cloud
58 // rows at all (proof no AWS call ran — and specifically NOT a
59 // *.cloudfront.net URL).
60 const url = deployed.site.url! as string;
61 expect(url).toMatch(
62 /^http:\/\/(localhost|127\.0\.0\.1|\[[0-9a-fA-F:]+\])/,
63 );
64 expect(deployed.site.distribution).toBeUndefined();
65 expect(deployed.site.server).toBeUndefined();
66 expect(deployed.site.bucket).toBeUndefined();
67 expect(deployed.site.files).toBeUndefined();
68
69 // The page serves through Vite's dev server (index.html transformed
70 // on the fly, module scripts served from source).
71 yield* expectUrlContains(`${url}/`, "VITE_AWS_PAGE_MARKER", {
72 timeout: "120 seconds",
73 label: "dev index page",
74 });
75 // Source modules serve straight from src/ (no build ran).
76 yield* expectUrlContains(
77 `${url}/src/main.ts`,
78 "VITE_AWS_MODULE_MARKER",
79 { label: "dev module source" },
80 );
81
82 // ── HMR surface: edit index.html in place. The stack is NOT
83 // re-applied — Vite's dev server serves the transformed html per
84 // request, so the same URL must show the new marker ───────────────
85 const indexPath = path.join(rootDir, "index.html");
86 const index = yield* fs.readFileString(indexPath);
87 yield* fs.writeFileString(
88 indexPath,
89 index.replace("VITE_AWS_PAGE_MARKER", "VITE_AWS_PAGE_MARKER_V2"),
90 );
91 yield* expectUrlContains(`${url}/`, "VITE_AWS_PAGE_MARKER_V2", {
92 timeout: "90 seconds",
93 label: "index page after edit",
94 });
95
96 yield* stack.destroy();
97 }),
98 { timeout: 600_000 },
99 );
100 },
101);