1import * as Alchemist from "@/Alchemist";
2import { describe, expect, test } from "alchemy-test";
3import * as Effect from "effect/Effect";
4
5describe("Alchemist runtime", { tags: ["unit", "local"] }, () => {
6 test("provides every service required by the programmatic stack API", () => {
7 const deploy = Effect.gen(function* () {
8 const snapshot = yield* Alchemist.Stack.plan({
9 operation: "deploy",
10 target: { entrypoint: "./alchemy.run.ts", stage: "prod" },
11 });
12 yield* Alchemist.Stack.apply(snapshot);
13 });
14 const runnable: Effect.Effect<void, unknown, never> = deploy.pipe(
15 Effect.provide(Alchemist.layer()),
16 Effect.scoped,
17 );
18
19 expect(Effect.isEffect(runnable)).toBe(true);
20 });
21
22 test("infers apply output from an explicitly supplied stack module", () => {
23 type Module = {
24 readonly default: Effect.Effect<{
25 readonly output: { readonly url: string };
26 }>;
27 };
28
29 const deploy = Effect.gen(function* () {
30 const snapshot = yield* Alchemist.Stack.plan<Module>({
31 operation: "deploy",
32 target: { entrypoint: "./alchemy.run.ts", stage: "prod" },
33 });
34 const output = yield* Alchemist.Stack.apply(snapshot);
35 const url: string = output.url;
36 return url;
37 });
38
39 expect(Effect.isEffect(deploy)).toBe(true);
40 });
41
42 test("filtered apply exposes no stack output", () => {
43 type Module = {
44 readonly default: Effect.Effect<{
45 readonly output: { readonly url: string };
46 }>;
47 };
48 const deploy = Effect.gen(function* () {
49 const snapshot = yield* Alchemist.Stack.plan<Module>({
50 operation: "deploy",
51 target: { entrypoint: "./alchemy.run.ts", stage: "prod" },
52 include: ["Branch"],
53 });
54 const output: undefined = yield* Alchemist.Stack.apply(snapshot);
55 return output;
56 });
57 expect(Effect.isEffect(deploy)).toBe(true);
58 });
59
60 test("legacy PlanInput stays full and optional filter inputs stay optional", () => {
61 type Equal<A, B> =
62 (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
63 ? true
64 : false;
65 type Output = { readonly url: string };
66 type Module = {
67 readonly default: Effect.Effect<{ readonly output: Output }>;
68 };
69 const input: Alchemist.Stack.PlanInput = {
70 operation: "deploy",
71 target: { entrypoint: "./alchemy.run.ts", stage: "prod" },
72 force: true,
73 };
74 const full = Alchemist.Stack.plan<Module>(input);
75 const excluded = Alchemist.Stack.plan<Module>({
76 ...input,
77 exclude: ["Other"],
78 });
79 const absent = Alchemist.Stack.plan<Module>({
80 ...input,
81 include: undefined,
82 exclude: undefined,
83 });
84 const optional = (input: Alchemist.Stack.FilteredPlanInput) =>
85 Alchemist.Stack.plan<Module>(input);
86 const optionalExclude = (exclude?: ReadonlyArray<string>) =>
87 Alchemist.Stack.plan<Module>({ ...input, exclude });
88 const assertions: [
89 Equal<Effect.Success<typeof full>["native"]["output"], Output>,
90 Equal<Effect.Success<typeof excluded>["native"]["output"], undefined>,
91 Equal<Effect.Success<typeof absent>["native"]["output"], Output>,
92 Equal<
93 Effect.Success<ReturnType<typeof optional>>["native"]["output"],
94 Output | undefined
95 >,
96 Equal<
97 Effect.Success<ReturnType<typeof optionalExclude>>["native"]["output"],
98 Output | undefined
99 >,
100 ] = [true, true, true, true, true];
101 expect(assertions.every(Boolean)).toBe(true);
102 });
103
104 test("full Alchemist inputs cannot erase known or optional filters", () => {
105 type Assignable<A, B> = [A] extends [B] ? true : false;
106 type Full = Alchemist.Stack.PlanInput;
107 type Base = Omit<Full, "include" | "exclude">;
108 const assignable: [
109 Assignable<Base & { include: ReadonlyArray<string> }, Full>,
110 Assignable<Base & { exclude: ReadonlyArray<string> }, Full>,
111 Assignable<Base & { include?: ReadonlyArray<string> }, Full>,
112 Assignable<Base & { exclude?: ReadonlyArray<string> }, Full>,
113 Assignable<Alchemist.Stack.FilteredPlanInput, Full>,
114 ] = [false, false, false, false, false];
115 expect(assignable.some(Boolean)).toBe(false);
116 });
117
118 test("preserves explicit module output inference through higher-order planning", () => {
119 type Equal<A, B> =
120 (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
121 ? true
122 : false;
123 type Output = { readonly url: string };
124 type Module = {
125 readonly default: Effect.Effect<{ readonly output: Output }>;
126 };
127 const input: Alchemist.Stack.PlanInput = {
128 operation: "deploy",
129 target: { entrypoint: "./alchemy.run.ts", stage: "prod" },
130 force: true,
131 };
132 const included = { ...input, include: ["Branch"] };
133 const excluded = { ...input, exclude: ["Other"] };
134 const optionalInput: Alchemist.Stack.FilteredPlanInput = input;
135 const full = Effect.succeed(input).pipe(
136 Effect.flatMap(Alchemist.Stack.plan<Module>),
137 );
138 const selected = Effect.succeed(included).pipe(
139 Effect.flatMap(Alchemist.Stack.plan<Module, typeof included>),
140 );
141 const excludedPlan = Effect.succeed(excluded).pipe(
142 Effect.flatMap(Alchemist.Stack.plan<Module, typeof excluded>),
143 );
144 const optional = Effect.succeed(optionalInput).pipe(
145 Effect.flatMap(
146 Alchemist.Stack.plan<Module, Alchemist.Stack.FilteredPlanInput>,
147 ),
148 );
149 const selectedCallback = Effect.succeed(included).pipe(
150 Effect.flatMap((input) => Alchemist.Stack.plan<Module>(input)),
151 );
152 const optionalCallback = Effect.succeed(optionalInput).pipe(
153 Effect.flatMap((input) => Alchemist.Stack.plan<Module>(input)),
154 );
155 const fullApply = full.pipe(Effect.flatMap(Alchemist.Stack.apply));
156 const selectedApply = selected.pipe(Effect.flatMap(Alchemist.Stack.apply));
157 const optionalApply = optional.pipe(Effect.flatMap(Alchemist.Stack.apply));
158 const assertions: [
159 Equal<Effect.Success<typeof full>["native"]["output"], Output>,
160 Equal<Effect.Success<typeof selected>["native"]["output"], undefined>,
161 Equal<Effect.Success<typeof excludedPlan>["native"]["output"], undefined>,
162 Equal<
163 Effect.Success<typeof optional>["native"]["output"],
164 Output | undefined
165 >,
166 Equal<
167 Effect.Success<typeof selectedCallback>["native"]["output"],
168 undefined
169 >,
170 Equal<
171 Effect.Success<typeof optionalCallback>["native"]["output"],
172 Output | undefined
173 >,
174 Equal<Effect.Success<typeof fullApply>, Output>,
175 Equal<Effect.Success<typeof selectedApply>, undefined>,
176 Equal<Effect.Success<typeof optionalApply>, Output | undefined>,
177 ] = [true, true, true, true, true, true, true, true, true];
178 expect(assertions.every(Boolean)).toBe(true);
179 });
180});