| 1 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import type * as FileSystem from "effect/FileSystem"; |
| 4 | import type * as Layer from "effect/Layer"; |
| 5 | import type * as Path from "effect/Path"; |
| 6 | import type * as HttpClient from "effect/http/HttpClient"; |
| 7 | import type * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 8 | import { AlchemyContext } from "../AlchemyContext.ts"; |
| 9 | import type { AuthProviders } from "../Auth/AuthProvider.ts"; |
| 10 | import type { ProfileStore } from "../Auth/Profile.ts"; |
| 11 | import { Stack } from "../Stack.ts"; |
| 12 | import { Stage } from "../Stage.ts"; |
| 13 | |
| 14 | /** What a secrets provider may depend on. */ |
| 15 | export type SecretsServices = |
| 16 | | Stage |
| 17 | | Stack |
| 18 | | AlchemyContext |
| 19 | | FileSystem.FileSystem |
| 20 | | Path.Path |
| 21 | | HttpClient.HttpClient |
| 22 | | ChildProcessSpawner.ChildProcessSpawner |
| 23 | | AuthProviders |
| 24 | | ProfileStore; |
| 25 | |
| 26 | /** What a `secrets` callback is given to decide its value. */ |
| 27 | export interface SecretsContext { |
| 28 | readonly stage: string; |
| 29 | readonly stack: Stack["Service"]; |
| 30 | readonly alchemyContext: AlchemyContext["Service"]; |
| 31 | } |
| 32 | |
| 33 | /** |
| 34 | * A value, or a callback that picks it from the {@link SecretsContext}. |
| 35 | * Both the stack's `secrets` list and every provider's options accept this. |
| 36 | */ |
| 37 | export type SecretsOption<T> = T | ((context: SecretsContext) => T); |
| 38 | |
| 39 | /** @internal */ |
| 40 | export const resolveSecretsOption = <T>( |
| 41 | option: SecretsOption<T>, |
| 42 | ): Effect.Effect<T, never, Stage | Stack | AlchemyContext> => |
| 43 | typeof option !== "function" |
| 44 | ? Effect.succeed(option) |
| 45 | : Effect.map( |
| 46 | Effect.all({ |
| 47 | stage: Stage, |
| 48 | stack: Stack, |
| 49 | alchemyContext: AlchemyContext, |
| 50 | }), |
| 51 | option as (context: SecretsContext) => T, |
| 52 | ); |
| 53 | |
| 54 | /** |
| 55 | * The process environment, and only that, for a secrets provider's own |
| 56 | * credentials and platform detection: `DOPPLER_TOKEN`, |
| 57 | * `INFISICAL_IDENTITY_ID`, `CI`, the OIDC variables. Nothing loaded by an |
| 58 | * earlier entry can stand in for them, and the stack's precedence is |
| 59 | * untouched, so a runner's exported variables reach the provider whatever |
| 60 | * the list says. `ALCHEMY_PROFILE` alone keeps coming from the pinned view |
| 61 | * the stack built the provider on, so `--profile` still selects the profile. |
| 62 | * |
| 63 | * @internal |
| 64 | */ |
| 65 | export const CredentialsConfig = ConfigProvider.layer( |
| 66 | Effect.map(ConfigProvider.ConfigProvider, (pinned) => { |
| 67 | const shell = ConfigProvider.fromEnv({ preserveEmptyStrings: true }); |
| 68 | return ConfigProvider.make((path) => |
| 69 | (path[0] === "ALCHEMY_PROFILE" ? pinned : shell).load(path), |
| 70 | ); |
| 71 | }), |
| 72 | ); |
| 73 | |
| 74 | /** The layer shape every secrets provider builds: it reads the configuration assembled so far and provides the merged one. */ |
| 75 | export type SecretsLayer = Layer.Layer<never, unknown, SecretsServices>; |
| 76 | |
| 77 | /** |
| 78 | * What a `secrets` entry must satisfy. Built-in providers are |
| 79 | * `Data.TaggedClass` instances, so a `_tag` identifies the kind of provider |
| 80 | * (the stack looks for the process environment this way) and `layer` reads |
| 81 | * the configuration assembled so far and provides the merged one. |
| 82 | * |
| 83 | * ```ts |
| 84 | * class VaultProvider extends Data.TaggedClass("my-app/SecretProvider::Vault")<{ |
| 85 | * readonly layer: Secrets.SecretsLayer; |
| 86 | * }> {} |
| 87 | * ``` |
| 88 | */ |
| 89 | export interface Provider { |
| 90 | readonly _tag: string; |
| 91 | readonly layer: SecretsLayer; |
| 92 | } |
| 93 | |
| 94 | /** A `secrets` entry: a provider, or any layer that provides a ConfigProvider. */ |
| 95 | export type SecretsEntry = Provider | SecretsLayer; |