1import * as machines from "@distilled.cloud/fly-io/machines";
2import * as Fly from "@/Fly";
3import * as Test from "@/Test/Alchemy";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import * as pathe from "pathe";
9import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
10import { expectUrlContains } from "../../Cloudflare/Utils/Http.ts";
11import { prepareNextjsFixture } from "../../Cloudflare/Website/TypeScriptCompat.ts";
12
13const { test } = Test.make({ providers: Fly.providers() });
14
15const logLevel = Effect.provideService(
16 MinimumLogLevel,
17 process.env.DEBUG ? "Debug" : "Info",
18);
19
20const fixtureDir = pathe.resolve(
21 import.meta.dirname,
22 "../../AWS/Website/fixtures/nextjs-app",
23);
24const fixtureEntries = [
25 ".gitignore",
26 "package.json",
27 "next.config.ts",
28 "tsconfig.json",
29 "app",
30 "public",
31];
32
33const waitUntilGone = (appName: string) =>
34 machines.getApp({ app_name: appName }).pipe(
35 Effect.as("found" as const),
36 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
37 Effect.repeat({
38 schedule: Schedule.spaced("1 second"),
39 until: (status) => status === "gone",
40 times: 10,
41 }),
42 );
43
44test.provider(
45 "Nextjs: deploy, GET /, destroy, gone",
46 (stack) =>
47 Effect.gen(function* () {
48 yield* stack.destroy();
49
50 // Clone OUTSIDE the repo (OS temp dir): an in-workspace clone makes
51 // Next treat the alchemy monorepo as the workspace root and look up
52 // the root's typescript (catalog:build = tsgo, which has no JS compiler).
53 const rootDir = yield* cloneFixture(fixtureDir, {
54 prefix: "alchemy-nextjs-fly-",
55 entries: fixtureEntries,
56 });
57 yield* prepareNextjsFixture(rootDir);
58
59 const deployed = yield* stack.deploy(
60 Effect.gen(function* () {
61 const site = yield* Fly.Website.Nextjs("Web", {
62 rootDir,
63 memo: {
64 include: [
65 "app/**",
66 "public/**",
67 "package.json",
68 "next.config.ts",
69 "tsconfig.json",
70 ],
71 },
72 });
73 return { site };
74 }),
75 );
76
77 const url = deployed.site.url;
78 expect(url).toBeDefined();
79 expect(url).toMatch(/^https:\/\//);
80 expect(deployed.site.service).toBeDefined();
81 expect(deployed.site.app).toBeDefined();
82
83 yield* expectUrlContains(`${url!}/`, "NEXTJS_AWS_PAGE_MARKER", {
84 timeout: "90 seconds",
85 label: "home page",
86 });
87 yield* expectUrlContains(
88 `${url!}/api/hello?echo=roundtrip`,
89 "NEXTJS_AWS_API_MARKER",
90 {
91 timeout: "30 seconds",
92 label: "api route",
93 },
94 );
95 yield* expectUrlContains(`${url!}/static`, "NEXTJS_AWS_STATIC_MARKER", {
96 timeout: "30 seconds",
97 label: "extra route",
98 });
99
100 const appName = deployed.site.app!.appName;
101 yield* stack.destroy();
102 const gone = yield* waitUntilGone(appName);
103 expect(gone).toEqual("gone");
104 }).pipe(logLevel),
105 {
106 tags: [
107 "provider:fly",
108 "provider:fly:machine",
109 "provider:fly:website",
110 "live",
111 ],
112 timeout: 480000,
113 },
114);