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 * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10import * as pathe from "pathe";
11import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
12import { expectUrlContains } from "../../Cloudflare/Utils/Http.ts";
13
14const { test } = Test.make({ providers: Fly.providers() });
15
16const logLevel = Effect.provideService(
17 MinimumLogLevel,
18 process.env.DEBUG ? "Debug" : "Info",
19);
20
21const fixtureDir = pathe.resolve(
22 import.meta.dirname,
23 "../../AWS/Website/fixtures/octane-app",
24);
25const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
26const fixtureEntries = [
27 ".gitignore",
28 "package.json",
29 "octane.config.ts",
30 "vite.config.ts",
31 "index.html",
32 "src",
33 "public",
34];
35
36const waitUntilGone = (appName: string) =>
37 machines.getApp({ app_name: appName }).pipe(
38 Effect.as("found" as const),
39 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
40 Effect.repeat({
41 schedule: Schedule.spaced("1 second"),
42 until: (status) => status === "gone",
43 times: 10,
44 }),
45 );
46
47test.provider(
48 "Octane: deploy, GET /, destroy, gone",
49 (stack) =>
50 Effect.gen(function* () {
51 yield* stack.destroy();
52
53 const rootDir = yield* cloneFixture(fixtureDir, {
54 prefix: "alchemy-octane-fly-",
55 tempRoot,
56 entries: fixtureEntries,
57 });
58
59 const fs = yield* FileSystem.FileSystem;
60 const pathMod = yield* Path.Path;
61 const configPath = pathMod.join(rootDir, "octane.config.ts");
62 const raw = yield* fs.readFileString(configPath);
63 expect(raw).not.toContain("adapter:");
64 expect(raw).not.toContain("@alchemy.run/frontend-frameworks");
65
66 const deployed = yield* stack.deploy(
67 Effect.gen(function* () {
68 const site = yield* Fly.Website.Octane("Web", {
69 rootDir,
70 memo: {
71 include: [
72 "src/**",
73 "public/**",
74 "package.json",
75 "octane.config.ts",
76 "vite.config.ts",
77 "index.html",
78 ],
79 },
80 });
81 return { site };
82 }),
83 );
84
85 expect(yield* fs.readFileString(configPath)).toBe(raw);
86 const url = deployed.site.url;
87 expect(url).toBeDefined();
88 expect(url).toMatch(/^https:\/\//);
89 expect(deployed.site.service).toBeDefined();
90 expect(deployed.site.app).toBeDefined();
91
92 yield* expectUrlContains(`${url!}/`, "OCTANE_AWS_PAGE_MARKER", {
93 timeout: "90 seconds",
94 label: "home page",
95 });
96 yield* expectUrlContains(
97 `${url!}/api/hello?echo=roundtrip`,
98 "OCTANE_AWS_API_MARKER",
99 {
100 timeout: "30 seconds",
101 label: "api route",
102 },
103 );
104
105 const appName = deployed.site.app!.appName;
106 yield* stack.destroy();
107 const gone = yield* waitUntilGone(appName);
108 expect(gone).toEqual("gone");
109 }).pipe(logLevel),
110 {
111 tags: [
112 "provider:fly",
113 "provider:fly:machine",
114 "provider:fly:website",
115 "live",
116 ],
117 timeout: 240000,
118 },
119);