1import * as Auth from "@distilled.cloud/aws/Auth";
2import * as EffectConsole from "effect/Console";
3import * as ConfigProvider from "effect/ConfigProvider";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import { AuthError } from "../../Auth/AuthProvider.ts";
8import { silentConsole } from "../../AWS/AuthProvider.ts";
9import {
10 bootstrap as bootstrapAws,
11 destroyBootstrap as destroyBootstrapAws,
12} from "../../AWS/Bootstrap.ts";
13import * as AWSCredentials from "../../AWS/Credentials.ts";
14import { AWSEnvironment } from "../../AWS/Environment.ts";
15import * as AWSRegion from "../../AWS/Region.ts";
16import { loadConfigProvider } from "../../Util/ConfigProvider.ts";
17import type { Target } from "../Session.ts";
18
19export interface AwsTarget extends Target {
20 readonly profile: string;
21 readonly region?: string;
22}
23
24const environment = Effect.fn(function* (target: AwsTarget) {
25 const ssoProfile = yield* Auth.loadProfile(target.profile);
26 if (!ssoProfile.sso_account_id) {
27 return yield* Effect.fail(
28 new AuthError({
29 message: `AWS SSO profile '${target.profile}' is missing sso_account_id`,
30 }),
31 );
32 }
33 const region = target.region ?? ssoProfile.region ?? "us-east-1";
34 // The credentials effect runs later, inside the built layer, where these
35 // services are no longer in context — capture them here so the route's
36 // requirements stay visible in its type.
37 const credentialServices =
38 yield* Effect.context<
39 Effect.Services<ReturnType<typeof Auth.loadProfileCredentials>>
40 >();
41 const aws = Layer.provideMerge(
42 Layer.mergeAll(AWSRegion.fromEnvironment, AWSCredentials.fromEnvironment),
43 Layer.succeed(
44 AWSEnvironment,
45 Effect.succeed({
46 accountId: ssoProfile.sso_account_id,
47 region,
48 credentials: Auth.loadProfileCredentials(target.profile).pipe(
49 Effect.provideService(EffectConsole.Console, silentConsole),
50 Effect.provide(credentialServices),
51 ),
52 profile: target.profile,
53 }),
54 ),
55 );
56 return {
57 accountId: ssoProfile.sso_account_id,
58 region,
59 layer: Layer.provide(
60 aws,
61 ConfigProvider.layer(
62 yield* loadConfigProvider(Option.fromNullishOr(target.envFile)),
63 ),
64 ),
65 };
66});
67
68/** Provision the AWS deployment assets bucket. */
69export const bootstrap = Effect.fn("Alchemist.provider.aws.bootstrap")(
70 function* (target: AwsTarget) {
71 const env = yield* environment(target);
72 const result = yield* Effect.provide(bootstrapAws(), env.layer);
73 return { accountId: env.accountId, region: env.region, ...result };
74 },
75);
76
77/** Destroy every Alchemy bootstrap bucket in the region. */
78export const teardown = Effect.fn("Alchemist.provider.aws.teardown")(function* (
79 target: AwsTarget,
80) {
81 const env = yield* environment(target);
82 const result = yield* Effect.provide(destroyBootstrapAws(), env.layer);
83 return {
84 accountId: env.accountId,
85 region: env.region,
86 destroyed: result.bucketNames,
87 };
88});