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", "nuxt-app");
16
17// Clone under the alchemy package so `nuxt`/`nitropack` 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 "nuxt.config.ts",
25 "app",
26 "server",
27 "public",
28];
29
30describe(
31 "AWS.Website.Nuxt local",
32 { tags: ["provider:aws", "provider:aws:website", "local"] },
33 () => {
34 test.provider(
35 "dev runs Nuxt's own dev server with no cloud resources",
36 (stack) =>
37 Effect.gen(function* () {
38 const fs = yield* FileSystem.FileSystem;
39 const path = yield* Path.Path;
40
41 yield* stack.destroy();
42
43 const rootDir = yield* cloneFixture(fixtureDir, {
44 prefix: "alchemy-nuxt-aws-local-",
45 tempRoot,
46 entries: fixtureEntries,
47 });
48
49 const deployed = yield* stack.deploy(
50 Effect.gen(function* () {
51 const site = yield* AWS.Website.Nuxt("NuxtSite", {
52 rootDir,
53 env: {
54 NUXT_PUBLIC_ENV_MARKER: "nuxt-aws-dev-env-marker",
55 },
56 });
57 return { site };
58 }),
59 );
60
61 // The site is the framework's own dev server: a localhost URL and
62 // no cloud rows at all (proof no AWS call ran).
63 const url = deployed.site.url! as string;
64 expect(url).toMatch(
65 /^http:\/\/(localhost|127\.0\.0\.1|\[[0-9a-fA-F:]+\])/,
66 );
67 expect(deployed.site.distribution).toBeUndefined();
68 expect(deployed.site.server).toBeUndefined();
69 expect(deployed.site.bucket).toBeUndefined();
70
71 // SSR page served by the nuxt dev server (native HMR toolchain).
72 yield* expectUrlContains(`${url}/`, "NUXT_AWS_PAGE_MARKER", {
73 timeout: "120 seconds",
74 label: "dev SSR home page",
75 });
76 // The fixture's own nuxt.config.ts applied in dev too.
77 yield* expectUrlContains(
78 `${url}/`,
79 "config:nuxt-aws-user-config-loaded",
80 { label: "user nuxt.config.ts applied (dev)" },
81 );
82 // server.environment reaches the dev server's process env — the
83 // same values the Lambda gets on deploy (dev/live parity).
84 yield* expectUrlContains(`${url}/`, "env:nuxt-aws-dev-env-marker", {
85 label: "server.environment injected into dev server",
86 });
87 // Server API route through the dev server.
88 yield* expectUrlContains(
89 `${url}/api/hello?echo=dev`,
90 "NUXT_AWS_API_MARKER",
91 { label: "API route (dev)" },
92 );
93
94 // ── HMR: edit the API route in place. The stack is NOT re-applied —
95 // nitro's dev rebuild must pick the change up and serve it through
96 // the same URL ───────────────────────────────────────────────────
97 const helloPath = path.join(rootDir, "server/api/hello.ts");
98 const hello = yield* fs.readFileString(helloPath);
99 yield* fs.writeFileString(
100 helloPath,
101 hello.replace("NUXT_AWS_API_MARKER", "NUXT_AWS_API_MARKER_V2"),
102 );
103 yield* expectUrlContains(
104 `${url}/api/hello?echo=dev`,
105 "NUXT_AWS_API_MARKER_V2",
106 { timeout: "90 seconds", label: "API route after HMR edit" },
107 );
108 // server.environment survived the dev rebuild — the injected env
109 // still reaches SSR after the reload (dev/live parity holds across
110 // hot updates, not just at boot).
111 yield* expectUrlContains(`${url}/`, "env:nuxt-aws-dev-env-marker", {
112 label: "server.environment after HMR edit",
113 });
114
115 yield* stack.destroy();
116 }),
117 { timeout: 600_000 },
118 );
119 },
120);