1import * as CliKit from "../CliKit/index.ts";
2import * as Effect from "effect/Effect";
3import * as Option from "effect/Option";
4import * as Command from "effect/cli/Command";
5import * as Flag from "effect/cli/Flag";
6import * as Aws from "../../Alchemist/routes/aws.ts";
7
8import { confirmOrDecline } from "./confirm.ts";
9import { envFile, yes } from "./flags.ts";
10import { instrumentCommand } from "./instrument.ts";
11
12const awsProfile = Flag.String("aws-profile").pipe(
13 Flag.withDescription("AWS CLI/SSO profile to use for bootstrap credentials"),
14 Flag.optional,
15 Flag.map(Option.getOrElse(() => "default")),
16);
17
18const awsRegion = Flag.String("region").pipe(
19 Flag.withDescription(
20 "AWS region to bootstrap (defaults to AWS_REGION env var)",
21 ),
22 Flag.optional,
23 Flag.map(Option.getOrUndefined),
24);
25
26const runBootstrap = Effect.fn(function* (args: {
27 envFile: Option.Option<string>;
28 profile: string;
29 region: string | undefined;
30 destroy: boolean;
31}) {
32 const { envFile, profile, region, destroy } = args;
33 const prompt = yield* CliKit.CliKit;
34 const target = {
35 profile,
36 region,
37 envFile: Option.getOrUndefined(envFile),
38 };
39 if (destroy) {
40 const result = yield* Aws.teardown(target);
41 yield* result.destroyed.length === 0
42 ? prompt.output.success("No bootstrap buckets found to destroy")
43 : prompt.output.success(
44 `Destroyed ${result.destroyed.length} bootstrap bucket(s): ${result.destroyed.join(", ")}`,
45 );
46 return;
47 }
48 const result = yield* Aws.bootstrap(target);
49 yield* result.created
50 ? prompt.output.success(`Created assets bucket: ${result.bucketName}`)
51 : prompt.output.success(
52 `Assets bucket already exists: ${result.bucketName}`,
53 );
54});
55
56const teardownCommand = Command.make(
57 "teardown",
58 { envFile, profile: awsProfile, region: awsRegion, yes },
59 instrumentCommand(
60 "provider.aws.teardown",
61 (a: { profile: string; region: string | undefined }) => ({
62 "aws.profile": a.profile,
63 "alchemy.region": a.region ?? "",
64 }),
65 )(
66 Effect.fn(function* ({ yes: approved, ...args }) {
67 yield* confirmOrDecline({
68 yes: approved,
69 message: "Destroy every Alchemy bootstrap bucket in this AWS region?",
70 confirmLabel: "Destroy",
71 cancelLabel: "Cancel",
72 });
73 yield* runBootstrap({ ...args, destroy: true });
74 }),
75 ),
76).pipe(Command.withDescription("Destroy the AWS deployment assets buckets"));
77
78const bootstrapCommand = Command.make(
79 "bootstrap",
80 { envFile, profile: awsProfile, region: awsRegion },
81 instrumentCommand(
82 "provider.aws.bootstrap",
83 (a: { profile: string; region: string | undefined }) => ({
84 "aws.profile": a.profile,
85 "alchemy.region": a.region ?? "",
86 }),
87 )(
88 Effect.fn(function* (args) {
89 yield* runBootstrap({ ...args, destroy: false });
90 }),
91 ),
92).pipe(Command.withDescription("Provision the AWS deployment assets bucket"));
93
94export const awsCommand = Command.make("aws", {}).pipe(
95 Command.withDescription("Manage AWS provider prerequisites"),
96 Command.withSubcommands([bootstrapCommand, teardownCommand]),
97);