1import * as Fly from "@/Fly";
2import * as Test from "@/Test/Alchemy";
3import { expect } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import { MinimumLogLevel } from "effect/References";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import {
9 materializeIsolatedProject,
10 removeIsolatedProject,
11} from "../IsolatedProject.ts";
12import IsolatedProjectApi, {
13 IsolatedSite,
14 project as serviceProject,
15} from "./fixtures/isolated-project-service.ts";
16import IsolatedProjectBox, {
17 project as spriteProject,
18} from "./fixtures/isolated-project-sprite.ts";
19
20const { test } = Test.make({ providers: Fly.providers() });
21
22const logLevel = Effect.provideService(
23 MinimumLogLevel,
24 process.env.DEBUG ? "Debug" : "Info",
25);
26
27const healthOk = (url: string) =>
28 HttpClient.get(new URL("/health", url)).pipe(
29 Effect.flatMap((res) =>
30 res.status === 200
31 ? res.json
32 : Effect.fail(new Error(`/health returned ${res.status}`)),
33 ),
34 Effect.retry({ schedule: Schedule.spaced("4 seconds"), times: 15 }),
35 );
36
37// Live proof that the Fly bun bootstrap (shared by Service and Sprite) boots
38// when `main` lives in an isolated project (see test/IsolatedProject.ts) —
39// the bundle `cwd` resolves none of alchemy's dependencies, so the
40// bootstrap's `@effect/platform-bun` / `alchemy/*` imports must be bundled
41// by the virtual-entry plugin rather than found from the project root. With
42// them left external bun dies at module load and the app never answers.
43test.provider(
44 "service bundled from an isolated project serves HTTP",
45 (stack) =>
46 Effect.gen(function* () {
47 yield* materializeIsolatedProject(serviceProject);
48 yield* stack.destroy();
49
50 try {
51 const deployed = yield* stack.deploy(
52 Effect.gen(function* () {
53 const app = yield* IsolatedSite;
54 yield* Fly.IpAssignment("IsolatedProjectShared", {
55 app,
56 type: "shared_v4",
57 });
58 return yield* IsolatedProjectApi;
59 }),
60 );
61 expect(deployed.state).toEqual("started");
62 expect(deployed.url).toBeTruthy();
63
64 expect(yield* healthOk(deployed.url!)).toEqual({ ok: true });
65
66 yield* stack.destroy();
67 } finally {
68 yield* removeIsolatedProject(serviceProject);
69 }
70 }).pipe(logLevel),
71 {
72 tags: [
73 "provider:fly",
74 "provider:fly:app",
75 "provider:fly:ipassignment",
76 "provider:fly:service",
77 "live",
78 ],
79 timeout: 180_000,
80 },
81);
82
83test.provider(
84 "sprite bundled from an isolated project serves HTTP",
85 (stack) =>
86 Effect.gen(function* () {
87 yield* materializeIsolatedProject(spriteProject);
88 yield* stack.destroy();
89
90 try {
91 const deployed = yield* stack.deploy(
92 Effect.gen(function* () {
93 return yield* IsolatedProjectBox;
94 }),
95 );
96 expect(deployed.url).toContain("sprites.app");
97
98 expect(yield* healthOk(deployed.url)).toEqual({ ok: true });
99
100 yield* stack.destroy();
101 } finally {
102 yield* removeIsolatedProject(spriteProject);
103 }
104 }).pipe(logLevel),
105 {
106 tags: [
107 "provider:fly",
108 "provider:fly:service",
109 "provider:fly:sprite",
110 "live",
111 ],
112 timeout: 180_000,
113 },
114);