1import * as Hetzner from "@/Hetzner";
2import * as Test from "@/Test/Alchemy";
3import * as servers from "@distilled.cloud/hetzner/servers";
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";
11
12const { test } = Test.make({ providers: Hetzner.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19const hasHetznerCreds = !!process.env.HCLOUD_TOKEN;
20
21const fixtureDir = pathe.resolve(
22 import.meta.dirname,
23 "../../Cloudflare/Website/foldkit-fixture",
24);
25const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
26const fixtureEntries = ["index.html", "package.json", "vite.config.ts", "src"];
27
28const waitUntilGone = (id: number) =>
29 servers.getServer({ id }).pipe(
30 Effect.as("found" as const),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("1 second"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 );
38
39test.provider.skipIf(!hasHetznerCreds)(
40 "Foldkit: deploy, GET /, destroy, gone",
41 (stack) =>
42 Effect.gen(function* () {
43 yield* stack.destroy();
44
45 const rootDir = yield* cloneFixture(fixtureDir, {
46 prefix: "alchemy-foldkit-hetzner-",
47 tempRoot,
48 entries: fixtureEntries,
49 });
50
51 const deployed = yield* stack.deploy(
52 Effect.gen(function* () {
53 const site = yield* Hetzner.Website.Foldkit("Web", {
54 rootDir,
55 memo: {
56 include: [
57 "index.html",
58 "src/**",
59 "package.json",
60 "vite.config.ts",
61 ],
62 },
63 });
64 return { site };
65 }),
66 );
67
68 const url = deployed.site.url;
69 expect(url).toBeDefined();
70 expect(url).toMatch(/^http:\/\//);
71 expect(deployed.site.service).toBeDefined();
72 expect(deployed.site.server).toBeDefined();
73
74 yield* expectUrlContains(`${url!}/`, "Foldkit Fixture", {
75 timeout: "90 seconds",
76 label: "home page",
77 });
78 yield* expectUrlContains(`${url!}/counter/42`, "Foldkit Fixture", {
79 timeout: "30 seconds",
80 label: "spa fallback",
81 });
82
83 const serverId = deployed.site.server!.serverId;
84 yield* stack.destroy();
85 const gone = yield* waitUntilGone(serverId);
86 expect(gone).toEqual("gone");
87 }).pipe(
88 logLevel,
89 Effect.ensuring(stack.destroy().pipe(Effect.orDie)),
90 Effect.catchTag(["PreconditionFailed", "Forbidden"], (error) =>
91 Effect.logWarning(`skipping: Hetzner quota (${error._tag})`),
92 ),
93 ),
94 {
95 tags: [
96 "provider:hetzner",
97 "provider:hetzner:service",
98 "provider:hetzner:website",
99 "live",
100 ],
101 timeout: 180000,
102 },
103);