@secrefs/node 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +331 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -1
- package/dist/index.d.ts +178 -1
- package/dist/index.js +326 -10
- package/dist/index.js.map +1 -1
- package/dist/secrefs.cjs +361 -18
- package/dist/secrefs.cjs.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/providers/aws.ts","../src/providers/base.ts","../src/ttlCache.ts","../src/controlPlaneClient.ts","../src/providers/vault.ts","../src/providers/local.ts","../src/providers/bitwarden.ts","../src/parser.ts","../src/resolver.ts","../src/envFile.ts"],"sourcesContent":["import { AwsSecretsManagerProvider } from \"./providers/aws.js\";\nimport { VaultProvider } from \"./providers/vault.js\";\nimport { LocalProvider } from \"./providers/local.js\";\nimport { BitwardenProvider } from \"./providers/bitwarden.js\";\nimport { isSecretRef } from \"./parser.js\";\nimport {\n expandKeyValueMap,\n expandProcessEnv,\n checkReferences,\n type ExpandOptions,\n type ProviderRegistry,\n} from \"./resolver.js\";\n\nexport {\n parseSecretRef,\n tryParseSecretRef,\n isSecretRef,\n SecRefParseError,\n type ParsedSecretRef,\n} from \"./parser.js\";\n\nexport { parseEnvFileText, recoverTruncatedSecRefs } from \"./envFile.js\";\n\nexport { TtlCache, type TtlCacheOptions } from \"./ttlCache.js\";\n\nexport {\n BaseSecretProvider,\n SecretFetchError,\n extractField,\n type ISecretProvider,\n type SecretFetchRequest,\n type ProviderHealth,\n} from \"./providers/base.js\";\n\nexport { AwsSecretsManagerProvider, type AwsProviderOptions } from \"./providers/aws.js\";\nexport { VaultProvider, type VaultProviderOptions } from \"./providers/vault.js\";\nexport { LocalProvider, type LocalProviderOptions } from \"./providers/local.js\";\nexport {\n BitwardenProvider,\n type BitwardenProviderOptions,\n type BitwardenClientLike,\n} from \"./providers/bitwarden.js\";\n\nexport {\n ControlPlaneClient,\n ControlPlaneRequestError,\n type ControlPlaneClientOptions,\n type ControlPlaneCredentialSource,\n type MintCredentialResponse,\n type MintedAwsCredentials,\n type MintedBitwardenCredentials,\n} from \"./controlPlaneClient.js\";\n\nexport {\n expandKeyValueMap,\n expandProcessEnv,\n checkReferences,\n SecRefsResolutionError,\n type ExpandOptions,\n type ProviderRegistry,\n type ResolutionFailure,\n type CheckResult,\n} from \"./resolver.js\";\n\n/** Builds the default provider registry: aws, vault, local, bitwarden. */\nexport function createDefaultProviders(): ProviderRegistry {\n return {\n aws: new AwsSecretsManagerProvider(),\n vault: new VaultProvider(),\n local: new LocalProvider(),\n bitwarden: new BitwardenProvider(),\n };\n}\n\nexport interface SecRefsOptions {\n providers?: ProviderRegistry;\n strict?: boolean;\n}\n\n/**\n * The primary library entry point. Instantiate your own (with a custom\n * provider registry) or use the default `secRefs` singleton below.\n */\nexport class SecRefs {\n readonly providers: ProviderRegistry;\n readonly strict: boolean;\n\n constructor(options: SecRefsOptions = {}) {\n this.providers = options.providers ?? createDefaultProviders();\n this.strict = options.strict ?? true;\n }\n\n private get expandOptions(): ExpandOptions {\n return { providers: this.providers, strict: this.strict };\n }\n\n /**\n * Expands every `sec://` value found in `process.env`, mutating it in\n * place. Returns the list of env var names that were rewritten.\n */\n async init(): Promise<string[]> {\n return expandProcessEnv(this.expandOptions);\n }\n\n /**\n * Expands `sec://` values in an arbitrary key/value map (e.g. a parsed\n * `.env` file) without touching `process.env`.\n */\n async expandEnv(env: Record<string, string | undefined>): Promise<Record<string, string>> {\n return expandKeyValueMap(env, this.expandOptions);\n }\n\n /** Expands a single string if it's a `sec://` reference; otherwise returns it unchanged. */\n async expandString(value: string): Promise<string> {\n if (!isSecretRef(value)) return value;\n const resolved = await expandKeyValueMap({ __value__: value }, this.expandOptions);\n return resolved.__value__ as string;\n }\n\n /**\n * Dry-run validation of every `sec://` reference in `env` (defaults to\n * `process.env`). Never returns plaintext secret values.\n */\n async check(\n env: Record<string, string | undefined> = process.env,\n ): Promise<Awaited<ReturnType<typeof checkReferences>>> {\n return checkReferences(env, this.expandOptions);\n }\n}\n\n/** Convenience singleton mirroring `secRefs.init()` / `secRefs.expandEnv()` / `secRefs.expandString()`. */\nexport const secRefs = new SecRefs();\n","import {\n GetSecretValueCommand,\n ListSecretsCommand,\n SecretsManagerClient,\n} from \"@aws-sdk/client-secrets-manager\";\nimport {\n BaseSecretProvider,\n errorMessage,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\nimport { TtlCache } from \"../ttlCache.js\";\nimport {\n ControlPlaneClient,\n ControlPlaneRequestError,\n type ControlPlaneCredentialSource,\n type MintedAwsCredentials,\n} from \"../controlPlaneClient.js\";\n\nexport type { ControlPlaneCredentialSource };\n\nexport interface AwsProviderOptions {\n region?: string;\n /** Inject a pre-configured client (primarily for testing) - also wins\n * over `controlPlane` if both are set, since a test that supplies an\n * explicit client wants full control regardless of the mode. */\n client?: SecretsManagerClient;\n /**\n * Sources per-request AWS credentials from a running control plane\n * (docs/control-plane-design.md §7/§10) instead of the ambient default\n * credential chain. Every `fetchOne` call mints a fresh, request-scoped\n * credential via `sts:AssumeRole` on the control plane's side - see\n * `apps/control-plane/src/providers/awsSts.ts`. Mutually exclusive\n * with ambient auth in spirit (not enforced - `client` still wins if\n * both are given, for testing).\n */\n controlPlane?: ControlPlaneCredentialSource;\n /**\n * How long a fetched secret value may be reused, in milliseconds.\n * Defaults to 0 - every expansion re-fetches, so a rotated secret\n * reaches a long-running consumer without a redeploy. Raise it to\n * trade a bounded window of staleness for fewer round trips. See\n * ../ttlCache.ts.\n */\n cacheTtlMs?: number;\n}\n\n/**\n * AWS Secrets Manager provider. Two credential-sourcing modes:\n *\n * - **Ambient (default)**: the AWS SDK v3 default credential provider\n * chain - environment variables, shared config/credentials files,\n * ECS/EC2 instance metadata, or an assumed IAM role. No credentials\n * ever need to live in SecRefs configuration itself. One client is\n * built lazily and reused for the provider's lifetime.\n * - **Control-plane-sourced** (`controlPlane` option): a fresh,\n * request-scoped credential is minted per `fetchOne` call via the\n * control plane's `/v1/credentials/mint`, so a distinct\n * `SecretsManagerClient` is constructed per path rather than reused -\n * each one only ever has the narrow scope that one mint granted.\n *\n * Raw secret values fetched per-path are cached in memory for the lifetime\n * of the provider instance either way, so multiple `#field` references\n * against the same secret only cost one API call (and, in control-plane\n * mode, one mint).\n */\nexport class AwsSecretsManagerProvider extends BaseSecretProvider {\n readonly name = \"aws\";\n\n private readonly explicitClient?: SecretsManagerClient;\n private readonly region?: string;\n private readonly controlPlane?: ControlPlaneCredentialSource;\n private readonly controlPlaneClient?: ControlPlaneClient;\n private ambientClient: SecretsManagerClient | null = null;\n private readonly rawCache: TtlCache<string>;\n\n constructor(options: AwsProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.region = options.region;\n this.controlPlane = options.controlPlane;\n this.rawCache = new TtlCache<string>({ ttlMs: options.cacheTtlMs });\n if (this.controlPlane) {\n this.controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n }\n }\n\n /** Resolves the `SecretsManagerClient` to use for one `path` - lazily\n * built and reused in ambient mode, freshly minted per call in\n * control-plane mode. `explicitClient` (test injection) always wins. */\n private async clientFor(path: string): Promise<SecretsManagerClient> {\n if (this.explicitClient) return this.explicitClient;\n\n if (this.controlPlane && this.controlPlaneClient) {\n const minted = await this.controlPlaneClient.mintCredential(this.controlPlane.alias, path);\n if (minted.provider !== \"aws\") {\n throw new Error(\n `control plane returned a \"${minted.provider}\" credential for alias \"${this.controlPlane.alias}\", ` +\n `expected \"aws\"`,\n );\n }\n return this.buildClientFromMintedCredentials(minted.credentials);\n }\n\n if (!this.ambientClient) this.ambientClient = new SecretsManagerClient({ region: this.region });\n return this.ambientClient;\n }\n\n private buildClientFromMintedCredentials(credentials: MintedAwsCredentials): SecretsManagerClient {\n return new SecretsManagerClient({\n region: this.region,\n credentials: {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n sessionToken: credentials.sessionToken,\n },\n });\n }\n\n private getRaw(path: string): Promise<string> {\n return this.rawCache.fetch(path, async () => {\n try {\n const client = await this.clientFor(path);\n const response = await client.send(new GetSecretValueCommand({ SecretId: path }));\n if (typeof response.SecretString === \"string\") {\n return response.SecretString;\n }\n if (response.SecretBinary) {\n return Buffer.from(response.SecretBinary as Uint8Array).toString(\"utf8\");\n }\n throw new Error(`secret \"${path}\" has no SecretString or SecretBinary payload`);\n } catch (err) {\n throw new Error(`could not fetch secret \"${path}\": ${errorMessage(err)}`);\n }\n });\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const raw = await this.getRaw(request.path);\n return extractField(raw, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n if (this.controlPlane) {\n // A control-plane-sourced provider has no single ambient\n // credential to probe - health here means \"the control plane is\n // reachable and this token is accepted\", checked with a\n // deliberately-unresolvable synthetic path so this never mutates\n // anything or depends on any specific secret existing. A 403\n // (\"no grant authorizes...\") still proves reachability + auth\n // worked; only a network/5xx failure means unhealthy.\n const controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n try {\n await controlPlaneClient.mintCredential(this.controlPlane.alias, \"__secrefs_health_check__\");\n } catch (err) {\n if (err instanceof ControlPlaneRequestError) {\n return { provider: this.name, ok: true, message: \"control plane reachable\" };\n }\n throw err;\n }\n return { provider: this.name, ok: true };\n }\n\n // A cheap, low-privilege call that proves both network reachability\n // and that the ambient credentials are valid enough to call the API.\n const client = await this.clientFor(\"__secrefs_health_check__\");\n await client.send(new ListSecretsCommand({ MaxResults: 1 }));\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","/**\n * The provider contract every SecRefs backend (AWS, Vault, local, or a\n * custom one you bring yourself) implements. Providers never log, print,\n * or persist the values they return - that discipline is enforced by the\n * resolver and CLI layers above them, which only ever handle secret values\n * long enough to hand them to `process.env` or a spawned child process.\n */\n\nexport interface SecretFetchRequest {\n /** The provider-specific secret path/id, as written after `sec://<provider>/`. */\n path: string;\n /** Optional dot-notation field to extract from a JSON secret payload. */\n field?: string;\n}\n\nexport interface ProviderHealth {\n provider: string;\n ok: boolean;\n /** Human-readable diagnostic. Never contains secret material. */\n message?: string;\n}\n\nexport interface ISecretProvider {\n readonly name: string;\n\n /** Fetch and resolve a single secret reference to its final string value. */\n fetchOne(request: SecretFetchRequest): Promise<string>;\n\n /**\n * Fetch multiple secret references. Implementations may batch/dedupe\n * against the backend where possible; the default behavior (provided by\n * {@link BaseSecretProvider}) is concurrent individual fetches via\n * `Promise.allSettled`, surfacing the first failure with full context.\n */\n fetchBatch(requests: SecretFetchRequest[]): Promise<string[]>;\n\n /**\n * Lightweight reachability/auth probe used by `secrefs check`. Must never\n * throw for expected failure modes (bad credentials, unreachable host) -\n * those are reported via the returned {@link ProviderHealth}.\n */\n healthCheck(): Promise<ProviderHealth>;\n}\n\nexport class SecretFetchError extends Error {\n constructor(\n public readonly provider: string,\n public readonly path: string,\n cause: unknown,\n ) {\n super(`[${provider}] failed to fetch secret at \"${path}\": ${errorMessage(cause)}`);\n this.name = \"SecretFetchError\";\n }\n}\n\nexport abstract class BaseSecretProvider implements ISecretProvider {\n abstract readonly name: string;\n\n abstract fetchOne(request: SecretFetchRequest): Promise<string>;\n\n async fetchBatch(requests: SecretFetchRequest[]): Promise<string[]> {\n const settled = await Promise.allSettled(requests.map((r) => this.fetchOne(r)));\n return settled.map((result, i) => {\n const request = requests[i];\n if (result.status === \"fulfilled\") {\n return result.value;\n }\n throw new SecretFetchError(this.name, request?.path ?? \"<unknown>\", result.reason);\n });\n }\n\n abstract healthCheck(): Promise<ProviderHealth>;\n}\n\nexport function errorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Extracts a (possibly dot-nested) field from a JSON-encoded secret. If no\n * field is requested, the raw string is returned unchanged. Throws a plain\n * `Error` (never leaking the secret value itself) when the payload isn't\n * valid JSON or the field path doesn't resolve to a value.\n */\nexport function extractField(\n raw: string,\n field: string | undefined,\n context: { provider: string; path: string },\n): string {\n if (!field) return raw;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `[${context.provider}] secret at \"${context.path}\" is not JSON, cannot extract field \"${field}\"`,\n );\n }\n\n let current: unknown = parsed;\n for (const part of field.split(\".\")) {\n if (current === null || typeof current !== \"object\") {\n throw new Error(\n `[${context.provider}] field \"${field}\" not found in secret at \"${context.path}\"`,\n );\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n if (current === undefined) {\n throw new Error(\n `[${context.provider}] field \"${field}\" not found in secret at \"${context.path}\"`,\n );\n }\n\n return typeof current === \"object\" ? JSON.stringify(current) : String(current);\n}\n","/**\n * A cache that expires, used by every provider that fetches over the\n * network.\n *\n * The default TTL is **zero** — every read re-fetches. That's deliberate\n * and it's the whole point of the product: a `sec://` reference is a\n * stable name for a value that changes underneath it. A consumer holding\n * the reference is supposed to see a rotated secret without being\n * redeployed, and a cache with no expiry silently breaks exactly that.\n * Before this existed, a long-running process fetched once and held the\n * old value until restart.\n *\n * A non-zero TTL is a real tradeoff, not a mistake: every expansion is a\n * network round trip, so a busy caller may want to trade a bounded window\n * of staleness for latency and API-rate-limit headroom. `ttlMs: 30_000`\n * means \"a rotation reaches me within 30 seconds\" — usually fine, and it\n * should be a decision someone made rather than a default they inherited.\n */\nexport interface TtlCacheOptions {\n /** Milliseconds an entry stays fresh. `0` (default) disables caching\n * entirely - every `fetch` call goes to the source. */\n ttlMs?: number;\n /** Injected in tests so expiry doesn't require real waiting. */\n now?: () => number;\n}\n\ninterface Entry<T> {\n value: Promise<T>;\n storedAt: number;\n}\n\nexport class TtlCache<T> {\n /** Settled values, only populated when a TTL is configured. */\n private readonly entries = new Map<string, Entry<T>>();\n /** Requests currently in flight, tracked separately from `entries`\n * because coalescing and caching are different things: sharing an\n * unsettled request holds no value past the moment it resolves, so it\n * stays correct even with caching fully disabled. */\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly ttlMs: number;\n private readonly now: () => number;\n\n constructor(options: TtlCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? 0;\n this.now = options.now ?? Date.now;\n }\n\n /**\n * Returns the cached value for `key` if it's still fresh, otherwise\n * calls `load` and caches that. In-flight promises are shared, so N\n * concurrent expansions of the same reference make one request rather\n * than N even when the TTL is zero - that's request coalescing, not\n * caching, and it doesn't hold a value past its use.\n *\n * A rejected load is evicted rather than remembered, so a transient\n * failure doesn't become a sticky one.\n */\n async fetch(key: string, load: () => Promise<T>): Promise<T> {\n // Always join an in-flight request, whatever the TTL.\n const pendingExisting = this.inFlight.get(key);\n if (pendingExisting) return pendingExisting;\n\n const cached = this.entries.get(key);\n if (cached && this.ttlMs > 0 && this.now() - cached.storedAt < this.ttlMs) {\n return cached.value;\n }\n\n const pending = load();\n this.inFlight.set(key, pending);\n\n try {\n const value = await pending;\n // Only retain past settlement when a TTL was actually asked for.\n if (this.ttlMs > 0) {\n this.entries.set(key, { value: Promise.resolve(value), storedAt: this.now() });\n }\n return value;\n } catch (err) {\n // Never remember a failure - a transient outage shouldn't become\n // a sticky one for the length of the TTL.\n this.entries.delete(key);\n throw err;\n } finally {\n this.inFlight.delete(key);\n }\n }\n\n /** Drops everything - used when a credential changes underneath the\n * cache and anything fetched with the old one is suspect. */\n clear(): void {\n this.entries.clear();\n }\n}\n","/**\n * Thin HTTP client for a running control plane's credential-broker\n * endpoint (docs/control-plane-design.md §7). This is the piece §10\n * flagged as the missing link: every provider that supports\n * control-plane-sourced credentials (AwsSecretsManagerProvider,\n * BitwardenProvider - see their `controlPlane` constructor option)\n * constructs one of these instead of only ever reading ambient env vars.\n *\n * Deliberately just an HTTP wrapper with no retry/backoff/circuit-\n * breaking logic - a mint failure surfaces as a normal rejected promise,\n * same as any other provider fetch failure, and the caller's existing\n * error handling (resolver.ts's Promise.allSettled aggregation) already\n * does the right thing with that.\n */\n\nexport interface MintedAwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken: string;\n /** ISO-8601 expiration timestamp. */\n expiration: string;\n}\n\nexport interface MintedBitwardenCredentials {\n accessToken: string;\n organizationId?: string;\n /** Explicitly not a TTL promise - see apps/control-plane/src/providers/bitwarden.ts. */\n note: string;\n}\n\nexport type MintCredentialResponse =\n | { provider: \"aws\"; credentials: MintedAwsCredentials }\n | { provider: \"bitwarden\"; credentials: MintedBitwardenCredentials };\n\n/** What a provider's `controlPlane` constructor option needs - shared\n * shape between `AwsSecretsManagerProvider` and `BitwardenProvider` (and\n * any future control-plane-aware provider). */\nexport interface ControlPlaneCredentialSource {\n /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */\n baseUrl: string;\n /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */\n token: string;\n /** Which `VaultConnection` alias this provider instance represents -\n * this is what the control plane's RBAC grants are actually scoped\n * against, not the `sec://` alias this provider happens to be\n * registered under (though in practice they're usually the same\n * string). */\n alias: string;\n /** Injected for testing - defaults to a real `ControlPlaneClient`. */\n client?: ControlPlaneClient;\n}\n\nexport interface ControlPlaneClientOptions {\n /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */\n baseUrl: string;\n /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */\n token: string;\n /** Injected for testing - defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Thrown for a well-formed error response from the control plane (401,\n * 403, 502, ...) - `status` and `message` come straight from its `{ error }`\n * body, so a denial reason (e.g. \"no grant authorizes path...\") reaches\n * the caller verbatim rather than as an opaque HTTP failure. */\nexport class ControlPlaneRequestError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = \"ControlPlaneRequestError\";\n }\n}\n\nexport class ControlPlaneClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: ControlPlaneClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.token = options.token;\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n /** Authenticates, authorizes, and resolves a credential for `alias`/`path`\n * - see the control plane's `POST /v1/credentials/mint`. Throws\n * `ControlPlaneRequestError` for any non-2xx response. */\n async mintCredential(alias: string, path: string): Promise<MintCredentialResponse> {\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}/v1/credentials/mint`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", authorization: `Bearer ${this.token}` },\n body: JSON.stringify({ alias, path }),\n });\n } catch (err) {\n throw new Error(\n `could not reach control plane at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n throw new ControlPlaneRequestError(\n response.status,\n body.error ?? `control plane returned ${response.status} for alias \"${alias}\" path \"${path}\"`,\n );\n }\n\n return (await response.json()) as MintCredentialResponse;\n }\n}\n","import vaultFactory from \"node-vault\";\nimport { TtlCache } from \"../ttlCache.js\";\nimport {\n BaseSecretProvider,\n errorMessage,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\n\nexport interface VaultProviderOptions {\n /** Defaults to $VAULT_ADDR. */\n endpoint?: string;\n /** Defaults to $VAULT_TOKEN. */\n token?: string;\n /** Inject a pre-configured client (primarily for testing). */\n client?: ReturnType<typeof vaultFactory>;\n /** How long a fetched secret may be reused, in ms. Defaults to 0 -\n * every expansion re-fetches, so rotation reaches a long-running\n * consumer without a redeploy. See ../ttlCache.ts. */\n cacheTtlMs?: number;\n}\n\n/**\n * HashiCorp Vault provider supporting both KV v1 and KV v2 secrets engines.\n * Auth is ambient via `VAULT_ADDR`/`VAULT_TOKEN` - point `path` at whatever\n * the Vault HTTP API itself expects (KV v2 mounts include a literal `data/`\n * segment, e.g. `secret/data/stripe`; KV v1 mounts do not).\n *\n * The client is constructed lazily on first use so that simply having a\n * `VaultProvider` in your provider registry doesn't require Vault to be\n * configured if you never actually reference `sec://vault/...`.\n */\nexport class VaultProvider extends BaseSecretProvider {\n readonly name = \"vault\";\n\n private readonly explicitClient?: ReturnType<typeof vaultFactory>;\n private readonly endpoint?: string;\n private readonly token?: string;\n private client: ReturnType<typeof vaultFactory> | null = null;\n private readonly dataCache: TtlCache<Record<string, unknown>>;\n\n constructor(options: VaultProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.endpoint = options.endpoint ?? process.env.VAULT_ADDR;\n this.token = options.token ?? process.env.VAULT_TOKEN;\n this.dataCache = new TtlCache<Record<string, unknown>>({ ttlMs: options.cacheTtlMs });\n }\n\n private getClient(): ReturnType<typeof vaultFactory> {\n if (this.explicitClient) return this.explicitClient;\n if (this.client) return this.client;\n\n if (!this.endpoint) {\n throw new Error(\"VAULT_ADDR is not set (required for sec://vault/... references)\");\n }\n if (!this.token) {\n throw new Error(\"VAULT_TOKEN is not set (required for sec://vault/... references)\");\n }\n\n this.client = vaultFactory({ endpoint: this.endpoint, token: this.token });\n return this.client;\n }\n\n private getData(path: string): Promise<Record<string, unknown>> {\n return this.dataCache.fetch(path, () =>\n this.getClient()\n .read(path)\n .then((response) => {\n const outer = response.data;\n if (outer === undefined || outer === null) {\n throw new Error(`no data returned for path \"${path}\"`);\n }\n // KV v2 responses nest the secret under data.data alongside\n // data.metadata; KV v1 responses put the secret straight in data.\n if (\n typeof outer === \"object\" &&\n \"data\" in (outer as Record<string, unknown>) &&\n \"metadata\" in (outer as Record<string, unknown>)\n ) {\n return (outer as Record<string, unknown>).data as Record<string, unknown>;\n }\n return outer as Record<string, unknown>;\n })\n .catch((err: unknown) => {\n throw new Error(`could not read Vault path \"${path}\": ${errorMessage(err)}`);\n }),\n );\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const data = await this.getData(request.path);\n\n if (!request.field) {\n const keys = Object.keys(data);\n // A single-key secret with no #field requested resolves to that\n // key's raw value directly (e.g. Vault's common { value: \"...\" }\n // convention); anything else is returned as a JSON blob.\n if (keys.length === 1) {\n const only = data[keys[0] as string];\n return typeof only === \"string\" ? only : JSON.stringify(only);\n }\n return JSON.stringify(data);\n }\n\n return extractField(JSON.stringify(data), request.field, {\n provider: this.name,\n path: request.path,\n });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.getClient().health();\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n BaseSecretProvider,\n type ProviderHealth,\n type SecretFetchRequest,\n extractField,\n} from \"./base.js\";\n\nconst DEFAULT_FILENAME = \".secrefs.local.json\";\n\nexport interface LocalProviderOptions {\n /** Overrides the file path. Defaults to $SECREFS_LOCAL_FILE or ./.secrefs.local.json */\n filePath?: string;\n /** Keep the parsed file in memory instead of re-reading per fetch.\n * Off by default so edits take effect immediately. */\n cacheFile?: boolean;\n}\n\n/**\n * Reads secrets from a gitignored, developer-local JSON file. Intended for\n * local development only - never point this at anything checked into\n * version control. Each top-level key is a secret path; its value is either\n * a plain string (returned as-is when no `#field` is requested) or an\n * object (JSON-stringified, then field-extracted as needed).\n *\n * Example `.secrefs.local.json`:\n * ```json\n * { \"mock-db\": { \"password\": \"hunter2\", \"user\": \"postgres\" } }\n * ```\n */\nexport class LocalProvider extends BaseSecretProvider {\n readonly name = \"local\";\n\n private readonly filePath: string;\n /** Re-read on every fetch. The file is local and tiny, and caching\n * it meant editing it mid-session silently did nothing. */\n private cache: Record<string, unknown> | null = null;\n private readonly cacheFile: boolean;\n\n constructor(options: LocalProviderOptions = {}) {\n super();\n this.filePath =\n options.filePath ??\n process.env.SECREFS_LOCAL_FILE ??\n path.join(process.cwd(), DEFAULT_FILENAME);\n this.cacheFile = options.cacheFile ?? false;\n }\n\n private async load(): Promise<Record<string, unknown>> {\n if (this.cache && this.cacheFile) return this.cache;\n\n let raw: string;\n try {\n raw = await readFile(this.filePath, \"utf8\");\n } catch (err) {\n throw new Error(\n `[local] could not read local secrets file at \"${this.filePath}\": ${\n err instanceof Error ? err.message : String(err)\n }. This file is gitignored by convention - see .secrefs.local.json in .gitignore.`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `[local] \"${this.filePath}\" is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`[local] \"${this.filePath}\" must contain a top-level JSON object`);\n }\n\n this.cache = parsed as Record<string, unknown>;\n return this.cache;\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const data = await this.load();\n if (!(request.path in data)) {\n throw new Error(`[local] no entry for path \"${request.path}\" in ${this.filePath}`);\n }\n\n const entry = data[request.path];\n const raw = typeof entry === \"string\" ? entry : JSON.stringify(entry);\n return extractField(raw, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.load();\n return { provider: this.name, ok: true, message: this.filePath };\n } catch (err) {\n return {\n provider: this.name,\n ok: false,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import { BitwardenClient } from \"@bitwarden/sdk-napi\";\nimport {\n BaseSecretProvider,\n errorMessage,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\nimport { ControlPlaneClient, type ControlPlaneCredentialSource } from \"../controlPlaneClient.js\";\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The subset of `@bitwarden/sdk-napi`'s `BitwardenClient` this provider\n * calls - kept narrow so tests can inject a plain mock object instead of\n * a real client, the same pattern `VaultProvider`/`AwsSecretsManagerProvider`\n * use for their own SDK clients. */\nexport interface BitwardenClientLike {\n auth(): { loginAccessToken(accessToken: string, stateFile?: string): Promise<void> };\n secrets(): {\n get(id: string): Promise<{ value: string }>;\n list(organizationId: string): Promise<{ data: { id: string; key: string }[] }>;\n };\n}\n\nexport interface BitwardenProviderOptions {\n /** Defaults to $BWS_ACCESS_TOKEN. Ignored if `controlPlane` is set. */\n accessToken?: string;\n /** Required only to resolve a `path` given as a secret *name* rather\n * than its UUID (see class docs). Defaults to $BWS_ORGANIZATION_ID.\n * Ignored if `controlPlane` is set - the control plane's distributed\n * credential supplies this instead. */\n organizationId?: string;\n /** Self-hosted instance override. Defaults to $BWS_API_URL. */\n apiUrl?: string;\n /** Self-hosted instance override. Defaults to $BWS_IDENTITY_URL. */\n identityUrl?: string;\n /**\n * Opt-in path to an encrypted session-state file the SDK can reuse\n * across calls to reduce auth rate-limiting (Bitwarden's own docs\n * describe this file's contents as fully encrypted, not plaintext\n * secret material). Omitted by default - this provider re-authenticates\n * in memory each time it's constructed and writes nothing to disk\n * unless a caller opts in.\n */\n stateFile?: string;\n /**\n * Sources the access token/organizationId from a running control plane\n * (docs/control-plane-design.md §7/§10, and §8 for why Bitwarden's\n * distribution here isn't the same as AWS's per-request minting -\n * see apps/control-plane/src/providers/bitwarden.ts). Every `fetchOne`\n * call still requests a distribution for its specific `path`, so the\n * control plane's RBAC `Grant.path_pattern` is enforced per secret even\n * though the underlying Bitwarden token itself isn't scoped that\n * narrowly - \"SDK-side enforcement\" as documented on the control-plane\n * side.\n */\n controlPlane?: ControlPlaneCredentialSource;\n /** Inject a pre-configured client (primarily for testing). */\n client?: BitwardenClientLike;\n}\n\n/**\n * Bitwarden **Secrets Manager** provider (not the password vault - see\n * https://bitwarden.com/help/secrets-manager-overview/). Two structural\n * differences from `AwsSecretsManagerProvider`/`VaultProvider` worth\n * knowing before using this:\n *\n * 1. **Secrets are end-to-end encrypted.** There is no plain authenticated\n * REST call to fetch a value - the official SDK derives a decryption\n * key from the access token during login and decrypts client-side.\n * That's why this provider depends on `@bitwarden/sdk-napi` (a beta\n * Node-API binding maintained by Bitwarden) rather than a bare `fetch`.\n * 2. **Bitwarden addresses secrets by UUID, with no path hierarchy** the\n * way AWS/Vault secret names have. `path` may be that UUID directly, or\n * - if `organizationId` is configured (ambient mode) or supplied by the\n * control plane (control-plane mode) - a human-readable secret *name*\n * (Bitwarden's \"key\" field), resolved via one cached `secrets().list()`\n * call. With neither, only UUID paths work.\n */\nexport class BitwardenProvider extends BaseSecretProvider {\n readonly name = \"bitwarden\";\n\n private readonly explicitClient?: BitwardenClientLike;\n private readonly ambientAccessToken?: string;\n private readonly ambientOrganizationId?: string;\n private readonly apiUrl?: string;\n private readonly identityUrl?: string;\n private readonly stateFile?: string;\n private readonly controlPlane?: ControlPlaneCredentialSource;\n private readonly controlPlaneClient?: ControlPlaneClient;\n\n private client: BitwardenClientLike | null = null;\n private loggedInAccessToken: string | null = null;\n private loggedIn: Promise<void> | null = null;\n private organizationId: string | undefined;\n /** Secret name -> id, populated by one `list()` call the first time a\n * non-UUID path is requested. Invalidated if `organizationId` ever\n * changes (control-plane mode, defensively - static in practice). */\n private nameToId: Promise<Map<string, string>> | null = null;\n\n constructor(options: BitwardenProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.apiUrl = options.apiUrl ?? process.env.BWS_API_URL;\n this.identityUrl = options.identityUrl ?? process.env.BWS_IDENTITY_URL;\n this.stateFile = options.stateFile;\n this.controlPlane = options.controlPlane;\n\n if (this.controlPlane) {\n this.controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n } else {\n this.ambientAccessToken = options.accessToken ?? process.env.BWS_ACCESS_TOKEN;\n this.ambientOrganizationId = options.organizationId ?? process.env.BWS_ORGANIZATION_ID;\n this.organizationId = this.ambientOrganizationId;\n }\n }\n\n private getClient(): BitwardenClientLike {\n if (this.explicitClient) return this.explicitClient;\n if (this.client) return this.client;\n this.client = new BitwardenClient({\n apiUrl: this.apiUrl,\n identityUrl: this.identityUrl,\n }) as unknown as BitwardenClientLike;\n return this.client;\n }\n\n private async loginWith(accessToken: string, organizationId: string | undefined): Promise<void> {\n if (organizationId !== this.organizationId) {\n this.nameToId = null; // stale cache keyed to a now-superseded org\n this.organizationId = organizationId;\n }\n if (this.loggedInAccessToken === accessToken && this.loggedIn) return this.loggedIn;\n\n this.loggedInAccessToken = accessToken;\n this.loggedIn = this.getClient()\n .auth()\n .loginAccessToken(accessToken, this.stateFile)\n .catch((err: unknown) => {\n this.loggedIn = null;\n this.loggedInAccessToken = null;\n throw new Error(`could not authenticate with the given access token: ${errorMessage(err)}`);\n });\n return this.loggedIn;\n }\n\n /** Ensures a session exists for `path`. Ambient mode logs in once\n * (memoized) with the ambient token; control-plane mode requests a\n * distribution for this specific `path` every call - see the\n * `controlPlane` option's docs for why that RBAC check has to be\n * per-path even though the token it returns doesn't vary. */\n private async ensureLoggedInFor(path: string): Promise<void> {\n if (!this.controlPlane) {\n if (!this.ambientAccessToken) {\n throw new Error(\"BWS_ACCESS_TOKEN is not set (required for sec://bitwarden/... references)\");\n }\n return this.loginWith(this.ambientAccessToken, this.ambientOrganizationId);\n }\n\n const minted = await this.controlPlaneClient!.mintCredential(this.controlPlane.alias, path);\n if (minted.provider !== \"bitwarden\") {\n throw new Error(\n `control plane returned a \"${minted.provider}\" credential for alias \"${this.controlPlane.alias}\", ` +\n `expected \"bitwarden\"`,\n );\n }\n return this.loginWith(minted.credentials.accessToken, minted.credentials.organizationId);\n }\n\n /** Assumes `ensureLoggedInFor(path)` has already run for this exact\n * `path` - callers always do that first, so `this.organizationId` is\n * already whatever this path's session resolved to. */\n private async resolveSecretId(path: string): Promise<string> {\n if (UUID_PATTERN.test(path)) return path;\n\n if (!this.organizationId) {\n throw new Error(\n `\"${path}\" is not a secret UUID, and no organizationId is available to look up a secret by name ` +\n `(set BWS_ORGANIZATION_ID, use the UUID directly, or - in control-plane mode - the distributed ` +\n `credential didn't include one)`,\n );\n }\n\n if (!this.nameToId) {\n const organizationId = this.organizationId;\n this.nameToId = (async () => {\n const { data } = await this.getClient().secrets().list(organizationId);\n return new Map(data.map((s) => [s.key, s.id]));\n })();\n }\n\n const map = await this.nameToId;\n const id = map.get(path);\n if (!id) {\n throw new Error(`no secret named \"${path}\" found in organization \"${this.organizationId}\"`);\n }\n return id;\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n let id: string;\n let secret: { value: string };\n try {\n // One ensureLoggedInFor per fetchOne - in control-plane mode this is\n // the one mint/RBAC-gate call for this path; resolveSecretId below\n // relies on it having already set this.organizationId.\n await this.ensureLoggedInFor(request.path);\n id = await this.resolveSecretId(request.path);\n secret = await this.getClient().secrets().get(id);\n } catch (err) {\n throw new Error(`could not fetch secret \"${request.path}\": ${errorMessage(err)}`);\n }\n return extractField(secret.value, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.ensureLoggedInFor(\"__secrefs_health_check__\");\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","/**\n * URI parser for SecRefs' `sec://` reference format:\n *\n * sec://<provider-alias>/<secret-path-or-id>[#<json-field>]\n *\n * sec://aws/prod/db#password\n * sec://vault/secret/data/stripe#key\n * sec://local/mock-db#password\n *\n * The provider alias is a bare identifier (letters/digits/`-`/`_`), the path\n * is opaque to this parser (providers interpret it however their backend\n * needs), and the optional `#field` fragment supports dot-notation for\n * traversing nested JSON secrets (e.g. `#nested.value`).\n */\n\nconst SEC_REF_PATTERN = /^sec:\\/\\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\\/([^\\s#]+)(?:#([^\\s#]+))?$/;\n\nexport interface ParsedSecretRef {\n /** The original, unmodified reference string. */\n raw: string;\n /** Lowercased provider alias, e.g. \"aws\", \"vault\", \"local\". */\n provider: string;\n /** The secret path/id as understood by the provider. */\n path: string;\n /** Optional dot-notation field to extract from a JSON secret. */\n field?: string;\n}\n\nexport class SecRefParseError extends Error {\n constructor(\n public readonly raw: string,\n public readonly reason: string,\n ) {\n super(`Invalid secret reference \"${raw}\": ${reason}`);\n this.name = \"SecRefParseError\";\n }\n}\n\n/** True if `value` is a string that looks like a `sec://` reference at all. */\nexport function isSecretRef(value: unknown): value is string {\n return typeof value === \"string\" && value.startsWith(\"sec://\");\n}\n\n/**\n * Parses a `sec://` reference string. Throws {@link SecRefParseError} if the\n * value isn't a string, doesn't start with `sec://`, or doesn't match the\n * full `<provider>/<path>[#field]` shape.\n */\nexport function parseSecretRef(raw: unknown): ParsedSecretRef {\n if (typeof raw !== \"string\") {\n throw new SecRefParseError(String(raw), \"reference must be a string\");\n }\n\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"sec://\")) {\n throw new SecRefParseError(raw, 'must start with \"sec://\"');\n }\n\n const match = SEC_REF_PATTERN.exec(trimmed);\n if (!match) {\n throw new SecRefParseError(\n raw,\n \"does not match sec://<provider>/<path>[#field] format\",\n );\n }\n\n const [, provider, path, field] = match;\n if (!provider) {\n throw new SecRefParseError(raw, \"missing provider alias\");\n }\n if (!path) {\n throw new SecRefParseError(raw, \"missing secret path\");\n }\n\n return {\n raw,\n provider: provider.toLowerCase(),\n path,\n field: field || undefined,\n };\n}\n\n/** Best-effort parse that returns `null` instead of throwing. */\nexport function tryParseSecretRef(raw: unknown): ParsedSecretRef | null {\n try {\n return parseSecretRef(raw);\n } catch {\n return null;\n }\n}\n","import { type ParsedSecretRef, isSecretRef, parseSecretRef } from \"./parser.js\";\nimport { errorMessage, type ISecretProvider, type SecretFetchRequest } from \"./providers/base.js\";\n\nexport type ProviderRegistry = Record<string, ISecretProvider>;\n\nexport interface ExpandOptions {\n providers: ProviderRegistry;\n /**\n * When true (default), a value that starts with `sec://` but fails to\n * parse throws immediately. When false, such values are left untouched.\n * This only affects syntactically malformed references - unknown\n * providers and provider-side fetch failures always surface as errors,\n * aggregated in a {@link SecRefsResolutionError}.\n */\n strict?: boolean;\n}\n\nexport interface ResolutionFailure {\n /** The env var / map key the reference was assigned to. */\n key: string;\n /** The original `sec://` string. */\n ref: string;\n message: string;\n}\n\nexport class SecRefsResolutionError extends Error {\n constructor(public readonly errors: ResolutionFailure[]) {\n super(\n `Failed to resolve ${errors.length} secret reference(s):\\n` +\n errors.map((e) => ` - ${e.key}: ${e.ref} -> ${e.message}`).join(\"\\n\"),\n );\n this.name = \"SecRefsResolutionError\";\n }\n}\n\nexport interface CheckResult {\n key: string;\n ref: string;\n provider: string;\n ok: boolean;\n /** Present only when ok is false. Never contains the secret value. */\n message?: string;\n}\n\nasync function resolveOne(\n ref: ParsedSecretRef,\n providers: ProviderRegistry,\n): Promise<string> {\n const provider = providers[ref.provider];\n if (!provider) {\n const available = Object.keys(providers).join(\", \") || \"none configured\";\n throw new Error(`unknown provider \"${ref.provider}\" (available: ${available})`);\n }\n const request: SecretFetchRequest = { path: ref.path, field: ref.field };\n return provider.fetchOne(request);\n}\n\n/**\n * Expands every `sec://` value in a plain key/value map, resolving all\n * references concurrently via `Promise.allSettled`. Non-reference values\n * pass through untouched. Never writes anything to disk - the caller\n * decides what to do with the returned map (assign to `process.env`,\n * template into a string, etc).\n *\n * Throws {@link SecRefsResolutionError} aggregating every failed\n * reference if any fail to resolve.\n */\nexport async function expandKeyValueMap(\n input: Record<string, string | undefined>,\n options: ExpandOptions,\n): Promise<Record<string, string>> {\n const strict = options.strict ?? true;\n const output: Record<string, string> = {};\n const pending: { key: string; ref: ParsedSecretRef }[] = [];\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined) continue;\n if (!isSecretRef(value)) {\n output[key] = value;\n continue;\n }\n try {\n pending.push({ key, ref: parseSecretRef(value) });\n } catch (err) {\n if (strict) throw err;\n output[key] = value;\n }\n }\n\n if (pending.length === 0) {\n return output;\n }\n\n const settled = await Promise.allSettled(\n pending.map(({ ref }) => resolveOne(ref, options.providers)),\n );\n\n const errors: ResolutionFailure[] = [];\n settled.forEach((result, i) => {\n const { key, ref } = pending[i] as { key: string; ref: ParsedSecretRef };\n if (result.status === \"fulfilled\") {\n output[key] = result.value;\n } else {\n errors.push({ key, ref: ref.raw, message: errorMessage(result.reason) });\n }\n });\n\n if (errors.length > 0) {\n throw new SecRefsResolutionError(errors);\n }\n\n return output;\n}\n\n/**\n * Expands `sec://` values found in `process.env`, mutating it in place.\n * Returns the list of env var names that were rewritten.\n */\nexport async function expandProcessEnv(options: ExpandOptions): Promise<string[]> {\n const resolved = await expandKeyValueMap(process.env, options);\n const changedKeys: string[] = [];\n for (const [key, value] of Object.entries(resolved)) {\n if (process.env[key] !== value) {\n process.env[key] = value;\n changedKeys.push(key);\n }\n }\n return changedKeys;\n}\n\n/**\n * Dry-run validation: resolves every `sec://` reference found in `input`\n * but reports only ok/failure per reference - the secret values themselves\n * are never returned or logged. Used by `secrefs check`.\n */\nexport async function checkReferences(\n input: Record<string, string | undefined>,\n options: ExpandOptions,\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n const parsedEntries: { key: string; ref: ParsedSecretRef }[] = [];\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || !isSecretRef(value)) continue;\n try {\n parsedEntries.push({ key, ref: parseSecretRef(value) });\n } catch (err) {\n results.push({ key, ref: value, provider: \"unknown\", ok: false, message: errorMessage(err) });\n }\n }\n\n const settled = await Promise.allSettled(\n parsedEntries.map(({ ref }) => resolveOne(ref, options.providers)),\n );\n\n settled.forEach((result, i) => {\n const { key, ref } = parsedEntries[i] as { key: string; ref: ParsedSecretRef };\n results.push({\n key,\n ref: ref.raw,\n provider: ref.provider,\n ok: result.status === \"fulfilled\",\n message: result.status === \"rejected\" ? errorMessage(result.reason) : undefined,\n });\n });\n\n return results;\n}\n","import { parse as parseDotenv } from \"dotenv\";\n\n// Matches an *unquoted* `KEY=sec://...` assignment, capturing the rest of\n// the line verbatim as the value.\nconst UNQUOTED_SEC_REF_LINE =\n /^[ \\t]*(?:export[ \\t]+)?([A-Za-z_][A-Za-z0-9_]*)[ \\t]*=[ \\t]*(sec:\\/\\/\\S.*)$/;\n\n/**\n * dotenv treats `#` as a start-of-comment marker even mid-value (unless\n * the value is quoted), which silently truncates the `#field` fragment\n * off an unquoted `sec://provider/path#field` reference - the exact\n * format SecRefs itself uses. Rather than require every `sec://` value in\n * `.env` to be quoted (an easy thing to forget, with no error if you do),\n * this re-scans the raw file for unquoted `sec://` assignments and\n * restores the value dotenv would otherwise clip.\n *\n * Note: because of this, an unquoted `sec://` value can't have a\n * trailing inline comment on the same line - put comments on their own\n * line instead. Quoted values (`KEY=\"sec://...#field\"`) are unaffected\n * and already handled correctly by dotenv itself.\n */\nexport function recoverTruncatedSecRefs(\n rawText: string,\n parsed: Record<string, string>,\n): Record<string, string> {\n const result = { ...parsed };\n for (const line of rawText.split(/\\r?\\n/)) {\n const match = UNQUOTED_SEC_REF_LINE.exec(line);\n if (!match) continue;\n const [, key, value] = match;\n if (!key || value === undefined) continue;\n result[key] = value.trimEnd();\n }\n return result;\n}\n\n/** Parses a `.env` file's contents, correctly preserving `sec://...#field` fragments. */\nexport function parseEnvFileText(rawText: string): Record<string, string> {\n const parsed = parseDotenv(rawText);\n return recoverTruncatedSecRefs(rawText, parsed);\n}\n"],"mappings":"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","names":["path","path","path","vaultFactory","path","path","path","path","parseDotenv"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/providers/aws.ts","../src/providers/errors.ts","../src/providers/base.ts","../src/ttlCache.ts","../src/controlPlaneClient.ts","../src/providers/vault.ts","../src/providers/local.ts","../src/providers/bitwarden.ts","../src/parser.ts","../src/resolver.ts","../src/envFile.ts","../src/config.ts"],"sourcesContent":["import { AwsSecretsManagerProvider } from \"./providers/aws.js\";\nimport { VaultProvider } from \"./providers/vault.js\";\nimport { LocalProvider } from \"./providers/local.js\";\nimport { BitwardenProvider } from \"./providers/bitwarden.js\";\nimport { isSecretRef } from \"./parser.js\";\nimport {\n expandKeyValueMap,\n expandProcessEnv,\n checkReferences,\n type ExpandOptions,\n type ProviderRegistry,\n} from \"./resolver.js\";\n\nexport {\n parseSecretRef,\n tryParseSecretRef,\n isSecretRef,\n SecRefParseError,\n type ParsedSecretRef,\n} from \"./parser.js\";\n\nexport { parseEnvFileText, recoverTruncatedSecRefs } from \"./envFile.js\";\n\nexport { TtlCache, type TtlCacheOptions } from \"./ttlCache.js\";\nexport {\n CONFIG_FILENAME,\n ConfigError,\n buildProviders,\n loadConfigFrom,\n parseConfig,\n type AliasConfig,\n type SecRefsConfig,\n} from \"./config.js\";\n\nexport {\n BaseSecretProvider,\n SecretFetchError,\n extractField,\n type ISecretProvider,\n type SecretFetchRequest,\n type ProviderHealth,\n} from \"./providers/base.js\";\n\nexport { AwsSecretsManagerProvider, type AwsProviderOptions } from \"./providers/aws.js\";\nexport { VaultProvider, type VaultProviderOptions } from \"./providers/vault.js\";\nexport { LocalProvider, type LocalProviderOptions } from \"./providers/local.js\";\nexport {\n BitwardenProvider,\n type BitwardenProviderOptions,\n type BitwardenClientLike,\n} from \"./providers/bitwarden.js\";\n\nexport {\n ControlPlaneClient,\n ControlPlaneRequestError,\n type ControlPlaneClientOptions,\n type ControlPlaneCredentialSource,\n type MintCredentialResponse,\n type MintedAwsCredentials,\n type MintedBitwardenCredentials,\n} from \"./controlPlaneClient.js\";\n\nexport {\n expandKeyValueMap,\n expandProcessEnv,\n checkReferences,\n SecRefsResolutionError,\n type ExpandOptions,\n type ProviderRegistry,\n type ResolutionFailure,\n type CheckResult,\n} from \"./resolver.js\";\n\n/** Builds the default provider registry: aws, vault, local, bitwarden. */\nexport function createDefaultProviders(): ProviderRegistry {\n return {\n aws: new AwsSecretsManagerProvider(),\n vault: new VaultProvider(),\n local: new LocalProvider(),\n bitwarden: new BitwardenProvider(),\n };\n}\n\nexport interface SecRefsOptions {\n providers?: ProviderRegistry;\n strict?: boolean;\n}\n\n/**\n * The primary library entry point. Instantiate your own (with a custom\n * provider registry) or use the default `secRefs` singleton below.\n */\nexport class SecRefs {\n readonly providers: ProviderRegistry;\n readonly strict: boolean;\n\n constructor(options: SecRefsOptions = {}) {\n this.providers = options.providers ?? createDefaultProviders();\n this.strict = options.strict ?? true;\n }\n\n private get expandOptions(): ExpandOptions {\n return { providers: this.providers, strict: this.strict };\n }\n\n /**\n * Expands every `sec://` value found in `process.env`, mutating it in\n * place. Returns the list of env var names that were rewritten.\n */\n async init(): Promise<string[]> {\n return expandProcessEnv(this.expandOptions);\n }\n\n /**\n * Expands `sec://` values in an arbitrary key/value map (e.g. a parsed\n * `.env` file) without touching `process.env`.\n */\n async expandEnv(env: Record<string, string | undefined>): Promise<Record<string, string>> {\n return expandKeyValueMap(env, this.expandOptions);\n }\n\n /** Expands a single string if it's a `sec://` reference; otherwise returns it unchanged. */\n async expandString(value: string): Promise<string> {\n if (!isSecretRef(value)) return value;\n const resolved = await expandKeyValueMap({ __value__: value }, this.expandOptions);\n return resolved.__value__ as string;\n }\n\n /**\n * Dry-run validation of every `sec://` reference in `env` (defaults to\n * `process.env`). Never returns plaintext secret values.\n */\n async check(\n env: Record<string, string | undefined> = process.env,\n ): Promise<Awaited<ReturnType<typeof checkReferences>>> {\n return checkReferences(env, this.expandOptions);\n }\n}\n\n/** Convenience singleton mirroring `secRefs.init()` / `secRefs.expandEnv()` / `secRefs.expandString()`. */\nexport const secRefs = new SecRefs();\n","import {\n GetSecretValueCommand,\n ListSecretsCommand,\n SecretsManagerClient,\n} from \"@aws-sdk/client-secrets-manager\";\nimport {\n BaseSecretProvider,\n errorMessage,\n SecretFetchError,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\nimport { fromNodeProviderChain } from \"@aws-sdk/credential-providers\";\nimport { TtlCache } from \"../ttlCache.js\";\nimport { classifyError, isStaleServable } from \"./errors.js\";\nimport {\n ControlPlaneClient,\n ControlPlaneRequestError,\n type ControlPlaneCredentialSource,\n type MintedAwsCredentials,\n} from \"../controlPlaneClient.js\";\n\nexport type { ControlPlaneCredentialSource };\n\nexport interface AwsProviderOptions {\n region?: string;\n /**\n * Named profile from the shared AWS config, for addressing more than\n * one account. Note this does not make a profile self-sufficient: with\n * SSO each profile needs its own live session, and they expire\n * independently - which is why an auth failure names the alias that\n * failed rather than just saying \"AWS\".\n */\n profile?: string;\n /** Inject a pre-configured client (primarily for testing) - also wins\n * over `controlPlane` if both are set, since a test that supplies an\n * explicit client wants full control regardless of the mode. */\n client?: SecretsManagerClient;\n /**\n * Sources per-request AWS credentials from a running control plane\n * (docs/control-plane-design.md §7/§10) instead of the ambient default\n * credential chain. Every `fetchOne` call mints a fresh, request-scoped\n * credential via `sts:AssumeRole` on the control plane's side - see\n * `apps/control-plane/src/providers/awsSts.ts`. Mutually exclusive\n * with ambient auth in spirit (not enforced - `client` still wins if\n * both are given, for testing).\n */\n controlPlane?: ControlPlaneCredentialSource;\n /**\n * How long a fetched secret value may be reused, in milliseconds.\n * Defaults to 0 - every expansion re-fetches, so a rotated secret\n * reaches a long-running consumer without a redeploy. Raise it to\n * trade a bounded window of staleness for fewer round trips. See\n * ../ttlCache.ts.\n */\n cacheTtlMs?: number;\n /**\n * Milliseconds a previously-fetched value may be served after a *failed*\n * refresh. Defaults to 0 (off). Only ever applies to transient faults -\n * network, timeout, throttle, 5xx. An expired credential or a denial is\n * never answered from a stale value, because both mean something in the\n * environment changed that a human has to see. See ../ttlCache.ts.\n */\n staleGraceMs?: number;\n /** Called when a stale value is served, so a CLI can warn. Receives the\n * secret path and the age of the value - never the value. */\n onStaleValue?: (path: string, ageMs: number, err: unknown) => void;\n}\n\n/**\n * AWS Secrets Manager provider. Two credential-sourcing modes:\n *\n * - **Ambient (default)**: the AWS SDK v3 default credential provider\n * chain - environment variables, shared config/credentials files,\n * ECS/EC2 instance metadata, or an assumed IAM role. No credentials\n * ever need to live in SecRefs configuration itself. One client is\n * built lazily and reused for the provider's lifetime.\n * - **Control-plane-sourced** (`controlPlane` option): a fresh,\n * request-scoped credential is minted per `fetchOne` call via the\n * control plane's `/v1/credentials/mint`, so a distinct\n * `SecretsManagerClient` is constructed per path rather than reused -\n * each one only ever has the narrow scope that one mint granted.\n *\n * Raw secret values fetched per-path are cached in memory for the lifetime\n * of the provider instance either way, so multiple `#field` references\n * against the same secret only cost one API call (and, in control-plane\n * mode, one mint).\n */\nexport class AwsSecretsManagerProvider extends BaseSecretProvider {\n readonly name = \"aws\";\n\n private readonly explicitClient?: SecretsManagerClient;\n private readonly region?: string;\n private readonly profile?: string;\n private readonly controlPlane?: ControlPlaneCredentialSource;\n private readonly controlPlaneClient?: ControlPlaneClient;\n private ambientClient: SecretsManagerClient | null = null;\n private readonly rawCache: TtlCache<string>;\n\n constructor(options: AwsProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.region = options.region;\n this.profile = options.profile;\n this.controlPlane = options.controlPlane;\n this.rawCache = new TtlCache<string>({\n ttlMs: options.cacheTtlMs,\n staleGraceMs: options.staleGraceMs,\n isStaleServable: (err) => isStaleServable(classifyError(err)),\n onStale: options.onStaleValue,\n });\n if (this.controlPlane) {\n this.controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n }\n }\n\n /** Resolves the `SecretsManagerClient` to use for one `path` - lazily\n * built and reused in ambient mode, freshly minted per call in\n * control-plane mode. `explicitClient` (test injection) always wins. */\n private async clientFor(path: string): Promise<SecretsManagerClient> {\n if (this.explicitClient) return this.explicitClient;\n\n if (this.controlPlane && this.controlPlaneClient) {\n const minted = await this.controlPlaneClient.mintCredential(this.controlPlane.alias, path);\n if (minted.provider !== \"aws\") {\n throw new Error(\n `control plane returned a \"${minted.provider}\" credential for alias \"${this.controlPlane.alias}\", ` +\n `expected \"aws\"`,\n );\n }\n return this.buildClientFromMintedCredentials(minted.credentials);\n }\n\n if (!this.ambientClient) {\n this.ambientClient = new SecretsManagerClient({\n region: this.region,\n // fromNodeProviderChain honours the same precedence as the\n // ambient default, just pinned to one profile - so instance\n // roles and env vars still work when no profile is named.\n ...(this.profile ? { credentials: fromNodeProviderChain({ profile: this.profile }) } : {}),\n });\n }\n return this.ambientClient;\n }\n\n private buildClientFromMintedCredentials(credentials: MintedAwsCredentials): SecretsManagerClient {\n return new SecretsManagerClient({\n region: this.region,\n credentials: {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n sessionToken: credentials.sessionToken,\n },\n });\n }\n\n private getRaw(path: string): Promise<string> {\n return this.rawCache.fetch(path, async () => {\n try {\n const client = await this.clientFor(path);\n const response = await client.send(new GetSecretValueCommand({ SecretId: path }));\n if (typeof response.SecretString === \"string\") {\n return response.SecretString;\n }\n if (response.SecretBinary) {\n return Buffer.from(response.SecretBinary as Uint8Array).toString(\"utf8\");\n }\n throw new Error(`secret \"${path}\" has no SecretString or SecretBinary payload`);\n } catch (err) {\n // SecretFetchError, not a plain Error: it classifies the cause, so\n // an expired SSO session is reported as an auth failure with a\n // remedy rather than as four broken secrets.\n if (err instanceof SecretFetchError) throw err;\n throw new SecretFetchError(this.name, path, err);\n }\n });\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const raw = await this.getRaw(request.path);\n return extractField(raw, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n if (this.controlPlane) {\n // A control-plane-sourced provider has no single ambient\n // credential to probe - health here means \"the control plane is\n // reachable and this token is accepted\", checked with a\n // deliberately-unresolvable synthetic path so this never mutates\n // anything or depends on any specific secret existing. A 403\n // (\"no grant authorizes...\") still proves reachability + auth\n // worked; only a network/5xx failure means unhealthy.\n const controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n try {\n await controlPlaneClient.mintCredential(this.controlPlane.alias, \"__secrefs_health_check__\");\n } catch (err) {\n if (err instanceof ControlPlaneRequestError) {\n return { provider: this.name, ok: true, message: \"control plane reachable\" };\n }\n throw err;\n }\n return { provider: this.name, ok: true };\n }\n\n // A cheap, low-privilege call that proves both network reachability\n // and that the ambient credentials are valid enough to call the API.\n const client = await this.clientFor(\"__secrefs_health_check__\");\n await client.send(new ListSecretsCommand({ MaxResults: 1 }));\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","/**\n * Why a fetch failed, and - more usefully - who has to do something\n * about it.\n *\n * The distinction that matters is between a problem with *the reference*\n * and a problem with *the environment*. They currently look identical to\n * a caller, which produces the worst message SecRefs emits today: an\n * expired `aws sso login` reported once per reference as\n * `could not fetch secret \"prod/db\"`, blaming four healthy secrets for\n * one dead credential.\n */\nexport type SecretErrorKind =\n /** Credentials are missing, expired, or unusable. Nothing about the\n * reference is wrong; a human has to re-authenticate. Report once for\n * the whole provider, never per reference. */\n | \"auth\"\n /** Credentials worked and the backend says this path does not exist.\n * Specific to the reference. */\n | \"not_found\"\n /** Credentials worked and the backend refused *this* path. Also\n * specific to the reference - sending someone to re-login when the\n * real problem is an IAM policy wastes their afternoon. */\n | \"denied\"\n /** Network, timeout, throttle, or 5xx. Nobody is at fault and the same\n * call may well succeed a second later. The only kind for which\n * serving a stale value is defensible. */\n | \"transient\"\n /** Unclassified. Treated as permanent, because guessing \"transient\"\n * would mean retrying something that will never succeed. */\n | \"unknown\";\n\n/** Error `name`s and codes the AWS SDK uses for credential problems. */\nconst AUTH_NAMES = new Set([\n \"CredentialsProviderError\",\n \"TokenProviderError\",\n \"ExpiredToken\",\n \"ExpiredTokenException\",\n \"InvalidClientTokenId\",\n \"UnrecognizedClientException\",\n \"InvalidIdentityToken\",\n \"AuthFailure\",\n \"SSOTokenProviderFailure\",\n]);\n\nconst NOT_FOUND_NAMES = new Set([\n \"ResourceNotFoundException\",\n \"NoSuchEntity\",\n \"SecretNotFound\",\n]);\n\nconst DENIED_NAMES = new Set([\n \"AccessDeniedException\",\n \"AccessDenied\",\n \"AuthorizationError\",\n \"UnauthorizedOperation\",\n]);\n\nconst TRANSIENT_NAMES = new Set([\n \"TimeoutError\",\n \"NetworkingError\",\n \"RequestTimeout\",\n \"RequestTimeoutException\",\n \"ThrottlingException\",\n \"TooManyRequestsException\",\n \"InternalServiceError\",\n \"InternalServerError\",\n \"ServiceUnavailable\",\n \"ServiceUnavailableException\",\n \"AbortError\",\n \"ECONNRESET\",\n \"ECONNREFUSED\",\n \"ETIMEDOUT\",\n \"EAI_AGAIN\",\n]);\n\n/** Message fragments to fall back on when a thrown value carries no\n * usable `name` - some SDK layers and every `fetch` polyfill lose it. */\nconst AUTH_FRAGMENTS = [\n \"could not load credentials\",\n \"sso session associated with this profile has expired\",\n \"security token included in the request is expired\",\n \"unable to locate credentials\",\n \"token is expired\",\n \"credentials have expired\",\n \"is expired\",\n];\n\nconst TRANSIENT_FRAGMENTS = [\n \"socket hang up\",\n \"network error\",\n \"timed out\",\n \"timeout\",\n \"econnreset\",\n \"econnrefused\",\n \"getaddrinfo\",\n];\n\nfunction nameOf(err: unknown): string {\n if (typeof err !== \"object\" || err === null) return \"\";\n const e = err as { name?: unknown; code?: unknown; __type?: unknown };\n for (const candidate of [e.name, e.code, e.__type]) {\n if (typeof candidate === \"string\" && candidate) return candidate;\n }\n return \"\";\n}\n\nfunction statusOf(err: unknown): number | undefined {\n if (typeof err !== \"object\" || err === null) return undefined;\n const meta = (err as { $metadata?: { httpStatusCode?: number } }).$metadata;\n if (typeof meta?.httpStatusCode === \"number\") return meta.httpStatusCode;\n const status = (err as { status?: unknown; statusCode?: unknown }).status ?? (err as { statusCode?: unknown }).statusCode;\n return typeof status === \"number\" ? status : undefined;\n}\n\n/**\n * Best-effort classification of a provider error. Deliberately\n * conservative: anything unrecognised is \"unknown\" rather than\n * \"transient\", because the only behaviour keyed off \"transient\" is\n * retrying and serving stale values, and doing either to a permanent\n * failure turns one clear error into a slow, confusing one.\n */\nexport function classifyError(err: unknown): SecretErrorKind {\n const name = nameOf(err);\n if (AUTH_NAMES.has(name)) return \"auth\";\n if (NOT_FOUND_NAMES.has(name)) return \"not_found\";\n if (DENIED_NAMES.has(name)) return \"denied\";\n if (TRANSIENT_NAMES.has(name)) return \"transient\";\n\n const status = statusOf(err);\n if (status === 401) return \"auth\";\n if (status === 403) return \"denied\";\n if (status === 404) return \"not_found\";\n if (status === 408 || status === 429) return \"transient\";\n if (status !== undefined && status >= 500) return \"transient\";\n\n const message = (err instanceof Error ? err.message : String(err ?? \"\")).toLowerCase();\n // Auth is checked before transient: \"the SSO session ... has expired\"\n // contains no transient marker, but a wrapped auth error can pick up\n // network-ish wording from an outer layer, and mis-filing auth as\n // transient is the expensive direction.\n if (AUTH_FRAGMENTS.some((f) => message.includes(f))) return \"auth\";\n if (TRANSIENT_FRAGMENTS.some((f) => message.includes(f))) return \"transient\";\n\n return \"unknown\";\n}\n\n/** Whether serving a previously-fetched value in place of this failure is\n * defensible. Only ever true for transient faults: a stale value papering\n * over an expired credential hides a change in the environment that a\n * human has to act on, and a stale value papering over a *rotation* means\n * continuing to use a key that may have been rotated because it leaked. */\nexport function isStaleServable(kind: SecretErrorKind): boolean {\n return kind === \"transient\";\n}\n\n/**\n * The action that actually fixes this, when there is one. Returned\n * separately from the message so a CLI can print it as a next step\n * rather than burying it in prose.\n */\nexport function remedyFor(kind: SecretErrorKind, provider: string, err?: unknown): string | undefined {\n if (kind !== \"auth\") return undefined;\n\n // The AWS SDK's own SSO message already names the fix; don't talk over\n // it with a worse guess about which profile is involved.\n const message = err instanceof Error ? err.message : \"\";\n if (/sso session/i.test(message)) {\n const profile = process.env.AWS_PROFILE;\n return profile\n ? `Run: aws sso login --profile ${profile}`\n : \"Run: aws sso login --profile <your-profile>\";\n }\n\n switch (provider) {\n case \"aws\": {\n const profile = process.env.AWS_PROFILE;\n return profile\n ? `Check credentials for AWS profile \"${profile}\" - if it uses SSO, run: aws sso login --profile ${profile}`\n : \"No AWS credentials found. Set AWS_PROFILE, export static keys, or attach an instance role.\";\n }\n case \"bitwarden\":\n return \"Set BWS_ACCESS_TOKEN to a valid Bitwarden machine account token.\";\n case \"vault\":\n return \"Set VAULT_ADDR and VAULT_TOKEN, or renew the token if it has expired.\";\n default:\n return undefined;\n }\n}\n","import { classifyError, remedyFor, type SecretErrorKind } from \"./errors.js\";\n\n/**\n * The provider contract every SecRefs backend (AWS, Vault, local, or a\n * custom one you bring yourself) implements. Providers never log, print,\n * or persist the values they return - that discipline is enforced by the\n * resolver and CLI layers above them, which only ever handle secret values\n * long enough to hand them to `process.env` or a spawned child process.\n */\n\nexport interface SecretFetchRequest {\n /** The provider-specific secret path/id, as written after `sec://<provider>/`. */\n path: string;\n /** Optional dot-notation field to extract from a JSON secret payload. */\n field?: string;\n}\n\nexport interface ProviderHealth {\n provider: string;\n ok: boolean;\n /** Human-readable diagnostic. Never contains secret material. */\n message?: string;\n}\n\nexport interface ISecretProvider {\n readonly name: string;\n\n /** Fetch and resolve a single secret reference to its final string value. */\n fetchOne(request: SecretFetchRequest): Promise<string>;\n\n /**\n * Fetch multiple secret references. Implementations may batch/dedupe\n * against the backend where possible; the default behavior (provided by\n * {@link BaseSecretProvider}) is concurrent individual fetches via\n * `Promise.allSettled`, surfacing the first failure with full context.\n */\n fetchBatch(requests: SecretFetchRequest[]): Promise<string[]>;\n\n /**\n * Lightweight reachability/auth probe used by `secrefs check`. Must never\n * throw for expected failure modes (bad credentials, unreachable host) -\n * those are reported via the returned {@link ProviderHealth}.\n */\n healthCheck(): Promise<ProviderHealth>;\n}\n\nexport class SecretFetchError extends Error {\n /** Whose problem this is - see {@link SecretErrorKind}. Classified from\n * `cause` so every existing throw site is categorised without having to\n * know about categories. */\n readonly kind: SecretErrorKind;\n /** The action that fixes it, when there is one (auth failures). */\n readonly remedy?: string;\n\n constructor(\n public readonly provider: string,\n public readonly path: string,\n public readonly cause: unknown,\n ) {\n const kind = classifyError(cause);\n // An auth failure has nothing to do with the path, and naming one\n // implies the reference is at fault. Four references failing on one\n // dead credential should not read as four broken secrets.\n super(\n kind === \"auth\"\n ? `[${provider}] cannot authenticate: ${errorMessage(cause)}`\n : `[${provider}] failed to fetch secret at \"${path}\": ${errorMessage(cause)}`,\n );\n this.name = \"SecretFetchError\";\n this.kind = kind;\n this.remedy = remedyFor(kind, provider, cause);\n }\n}\n\nexport abstract class BaseSecretProvider implements ISecretProvider {\n abstract readonly name: string;\n\n abstract fetchOne(request: SecretFetchRequest): Promise<string>;\n\n async fetchBatch(requests: SecretFetchRequest[]): Promise<string[]> {\n const settled = await Promise.allSettled(requests.map((r) => this.fetchOne(r)));\n return settled.map((result, i) => {\n const request = requests[i];\n if (result.status === \"fulfilled\") {\n return result.value;\n }\n throw new SecretFetchError(this.name, request?.path ?? \"<unknown>\", result.reason);\n });\n }\n\n abstract healthCheck(): Promise<ProviderHealth>;\n}\n\nexport function errorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Extracts a (possibly dot-nested) field from a JSON-encoded secret. If no\n * field is requested, the raw string is returned unchanged. Throws a plain\n * `Error` (never leaking the secret value itself) when the payload isn't\n * valid JSON or the field path doesn't resolve to a value.\n */\nexport function extractField(\n raw: string,\n field: string | undefined,\n context: { provider: string; path: string },\n): string {\n if (!field) return raw;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `[${context.provider}] secret at \"${context.path}\" is not JSON, cannot extract field \"${field}\"`,\n );\n }\n\n let current: unknown = parsed;\n for (const part of field.split(\".\")) {\n if (current === null || typeof current !== \"object\") {\n throw new Error(\n `[${context.provider}] field \"${field}\" not found in secret at \"${context.path}\"`,\n );\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n if (current === undefined) {\n throw new Error(\n `[${context.provider}] field \"${field}\" not found in secret at \"${context.path}\"`,\n );\n }\n\n return typeof current === \"object\" ? JSON.stringify(current) : String(current);\n}\n","/**\n * A cache that expires, used by every provider that fetches over the\n * network.\n *\n * The default TTL is **zero** — every read re-fetches. That's deliberate\n * and it's the whole point of the product: a `sec://` reference is a\n * stable name for a value that changes underneath it. A consumer holding\n * the reference is supposed to see a rotated secret without being\n * redeployed, and a cache with no expiry silently breaks exactly that.\n * Before this existed, a long-running process fetched once and held the\n * old value until restart.\n *\n * A non-zero TTL is a real tradeoff, not a mistake: every expansion is a\n * network round trip, so a busy caller may want to trade a bounded window\n * of staleness for latency and API-rate-limit headroom. `ttlMs: 30_000`\n * means \"a rotation reaches me within 30 seconds\" — usually fine, and it\n * should be a decision someone made rather than a default they inherited.\n */\nexport interface TtlCacheOptions {\n /** Milliseconds an entry stays fresh. `0` (default) disables caching\n * entirely - every `fetch` call goes to the source. */\n ttlMs?: number;\n /**\n * Milliseconds a *previously successful* value may be served after a\n * failed refresh. `0` (default) means a failure is a failure.\n *\n * This exists for one narrow case: use-time resolution couples every\n * use to the vault being reachable right now, so a two-second network\n * blip can fail a request that would otherwise have been fine. A short\n * grace window rides that out.\n *\n * It is emphatically not a general fallback, and `isStaleServable`\n * below is what keeps it honest. Serving a stale value over an expired\n * credential hides a change the operator has to act on; serving one\n * over a rotation means continuing to use a key that may have been\n * rotated *because it leaked*. Keep the window short.\n */\n staleGraceMs?: number;\n /**\n * Decides whether a given failure may be answered from the stale\n * value. Defaults to \"never\". Providers pass a predicate that admits\n * only transient faults - the cache itself stays free of any knowledge\n * about provider error taxonomies.\n */\n isStaleServable?: (err: unknown) => boolean;\n /** Called when a stale value is served, so the layer above can warn.\n * Never receives the value - only the key and its age. */\n onStale?: (key: string, ageMs: number, err: unknown) => void;\n /** Injected in tests so expiry doesn't require real waiting. */\n now?: () => number;\n}\n\ninterface Entry<T> {\n value: Promise<T>;\n storedAt: number;\n}\n\n/** Thrown value carried alongside the stale answer, so a caller that\n * wants to know it got a stale value can, without the cache having to\n * invent a wrapper type for the success path. */\nexport interface StaleServeInfo {\n key: string;\n ageMs: number;\n error: unknown;\n}\n\nexport class TtlCache<T> {\n /** Settled values, only populated when a TTL is configured. */\n private readonly entries = new Map<string, Entry<T>>();\n /** Requests currently in flight, tracked separately from `entries`\n * because coalescing and caching are different things: sharing an\n * unsettled request holds no value past the moment it resolves, so it\n * stays correct even with caching fully disabled. */\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly ttlMs: number;\n private readonly staleGraceMs: number;\n private readonly isStaleServable: (err: unknown) => boolean;\n private readonly onStale?: (key: string, ageMs: number, err: unknown) => void;\n private readonly now: () => number;\n\n constructor(options: TtlCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? 0;\n this.staleGraceMs = options.staleGraceMs ?? 0;\n this.isStaleServable = options.isStaleServable ?? (() => false);\n this.onStale = options.onStale;\n this.now = options.now ?? Date.now;\n }\n\n /**\n * Returns the cached value for `key` if it's still fresh, otherwise\n * calls `load` and caches that. In-flight promises are shared, so N\n * concurrent expansions of the same reference make one request rather\n * than N even when the TTL is zero - that's request coalescing, not\n * caching, and it doesn't hold a value past its use.\n *\n * A rejected load is evicted rather than remembered, so a transient\n * failure doesn't become a sticky one.\n */\n async fetch(key: string, load: () => Promise<T>): Promise<T> {\n // Always join an in-flight request, whatever the TTL.\n const pendingExisting = this.inFlight.get(key);\n if (pendingExisting) return pendingExisting;\n\n const cached = this.entries.get(key);\n if (cached && this.ttlMs > 0 && this.now() - cached.storedAt < this.ttlMs) {\n return cached.value;\n }\n\n const pending = load();\n this.inFlight.set(key, pending);\n\n try {\n const value = await pending;\n // Retain past settlement when a TTL was asked for, or when a stale\n // grace window was - the latter needs a previous value to fall back\n // on, but never serves it as fresh (the freshness check above still\n // requires ttlMs > 0).\n if (this.ttlMs > 0 || this.staleGraceMs > 0) {\n this.entries.set(key, { value: Promise.resolve(value), storedAt: this.now() });\n }\n return value;\n } catch (err) {\n const previous = this.entries.get(key);\n if (previous && this.staleGraceMs > 0 && this.isStaleServable(err)) {\n const ageMs = this.now() - previous.storedAt;\n if (ageMs <= this.staleGraceMs) {\n // Deliberately does NOT refresh storedAt: the grace window runs\n // from the last *successful* fetch, so a provider that stays\n // down cannot be ridden indefinitely one failure at a time.\n this.onStale?.(key, ageMs, err);\n return previous.value;\n }\n }\n // Never remember a failure - a transient outage shouldn't become\n // a sticky one for the length of the TTL.\n this.entries.delete(key);\n throw err;\n } finally {\n this.inFlight.delete(key);\n }\n }\n\n /** Drops everything - used when a credential changes underneath the\n * cache and anything fetched with the old one is suspect. */\n clear(): void {\n this.entries.clear();\n }\n}\n","/**\n * Thin HTTP client for a running control plane's credential-broker\n * endpoint (docs/control-plane-design.md §7). This is the piece §10\n * flagged as the missing link: every provider that supports\n * control-plane-sourced credentials (AwsSecretsManagerProvider,\n * BitwardenProvider - see their `controlPlane` constructor option)\n * constructs one of these instead of only ever reading ambient env vars.\n *\n * Deliberately just an HTTP wrapper with no retry/backoff/circuit-\n * breaking logic - a mint failure surfaces as a normal rejected promise,\n * same as any other provider fetch failure, and the caller's existing\n * error handling (resolver.ts's Promise.allSettled aggregation) already\n * does the right thing with that.\n */\n\nexport interface MintedAwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken: string;\n /** ISO-8601 expiration timestamp. */\n expiration: string;\n}\n\nexport interface MintedBitwardenCredentials {\n accessToken: string;\n organizationId?: string;\n /** Explicitly not a TTL promise - see apps/control-plane/src/providers/bitwarden.ts. */\n note: string;\n}\n\nexport type MintCredentialResponse =\n | { provider: \"aws\"; credentials: MintedAwsCredentials }\n | { provider: \"bitwarden\"; credentials: MintedBitwardenCredentials };\n\n/** What a provider's `controlPlane` constructor option needs - shared\n * shape between `AwsSecretsManagerProvider` and `BitwardenProvider` (and\n * any future control-plane-aware provider). */\nexport interface ControlPlaneCredentialSource {\n /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */\n baseUrl: string;\n /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */\n token: string;\n /** Which `VaultConnection` alias this provider instance represents -\n * this is what the control plane's RBAC grants are actually scoped\n * against, not the `sec://` alias this provider happens to be\n * registered under (though in practice they're usually the same\n * string). */\n alias: string;\n /** Injected for testing - defaults to a real `ControlPlaneClient`. */\n client?: ControlPlaneClient;\n}\n\nexport interface ControlPlaneClientOptions {\n /** Base URL of a running control plane, e.g. from $SECREFS_CONTROL_PLANE_URL. */\n baseUrl: string;\n /** Bootstrap token or a verified OIDC token, e.g. from $SECREFS_CONTROL_PLANE_TOKEN. */\n token: string;\n /** Injected for testing - defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Thrown for a well-formed error response from the control plane (401,\n * 403, 502, ...) - `status` and `message` come straight from its `{ error }`\n * body, so a denial reason (e.g. \"no grant authorizes path...\") reaches\n * the caller verbatim rather than as an opaque HTTP failure. */\nexport class ControlPlaneRequestError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = \"ControlPlaneRequestError\";\n }\n}\n\nexport class ControlPlaneClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: ControlPlaneClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.token = options.token;\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n /** Authenticates, authorizes, and resolves a credential for `alias`/`path`\n * - see the control plane's `POST /v1/credentials/mint`. Throws\n * `ControlPlaneRequestError` for any non-2xx response. */\n async mintCredential(alias: string, path: string): Promise<MintCredentialResponse> {\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.baseUrl}/v1/credentials/mint`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", authorization: `Bearer ${this.token}` },\n body: JSON.stringify({ alias, path }),\n });\n } catch (err) {\n throw new Error(\n `could not reach control plane at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n throw new ControlPlaneRequestError(\n response.status,\n body.error ?? `control plane returned ${response.status} for alias \"${alias}\" path \"${path}\"`,\n );\n }\n\n return (await response.json()) as MintCredentialResponse;\n }\n}\n","import vaultFactory from \"node-vault\";\nimport { TtlCache } from \"../ttlCache.js\";\nimport {\n BaseSecretProvider,\n errorMessage,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\n\nexport interface VaultProviderOptions {\n /** Defaults to $VAULT_ADDR. */\n endpoint?: string;\n /** Defaults to $VAULT_TOKEN. */\n token?: string;\n /** Inject a pre-configured client (primarily for testing). */\n client?: ReturnType<typeof vaultFactory>;\n /** How long a fetched secret may be reused, in ms. Defaults to 0 -\n * every expansion re-fetches, so rotation reaches a long-running\n * consumer without a redeploy. See ../ttlCache.ts. */\n cacheTtlMs?: number;\n}\n\n/**\n * HashiCorp Vault provider supporting both KV v1 and KV v2 secrets engines.\n * Auth is ambient via `VAULT_ADDR`/`VAULT_TOKEN` - point `path` at whatever\n * the Vault HTTP API itself expects (KV v2 mounts include a literal `data/`\n * segment, e.g. `secret/data/stripe`; KV v1 mounts do not).\n *\n * The client is constructed lazily on first use so that simply having a\n * `VaultProvider` in your provider registry doesn't require Vault to be\n * configured if you never actually reference `sec://vault/...`.\n */\nexport class VaultProvider extends BaseSecretProvider {\n readonly name = \"vault\";\n\n private readonly explicitClient?: ReturnType<typeof vaultFactory>;\n private readonly endpoint?: string;\n private readonly token?: string;\n private client: ReturnType<typeof vaultFactory> | null = null;\n private readonly dataCache: TtlCache<Record<string, unknown>>;\n\n constructor(options: VaultProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.endpoint = options.endpoint ?? process.env.VAULT_ADDR;\n this.token = options.token ?? process.env.VAULT_TOKEN;\n this.dataCache = new TtlCache<Record<string, unknown>>({ ttlMs: options.cacheTtlMs });\n }\n\n private getClient(): ReturnType<typeof vaultFactory> {\n if (this.explicitClient) return this.explicitClient;\n if (this.client) return this.client;\n\n if (!this.endpoint) {\n throw new Error(\"VAULT_ADDR is not set (required for sec://vault/... references)\");\n }\n if (!this.token) {\n throw new Error(\"VAULT_TOKEN is not set (required for sec://vault/... references)\");\n }\n\n this.client = vaultFactory({ endpoint: this.endpoint, token: this.token });\n return this.client;\n }\n\n private getData(path: string): Promise<Record<string, unknown>> {\n return this.dataCache.fetch(path, () =>\n this.getClient()\n .read(path)\n .then((response) => {\n const outer = response.data;\n if (outer === undefined || outer === null) {\n throw new Error(`no data returned for path \"${path}\"`);\n }\n // KV v2 responses nest the secret under data.data alongside\n // data.metadata; KV v1 responses put the secret straight in data.\n if (\n typeof outer === \"object\" &&\n \"data\" in (outer as Record<string, unknown>) &&\n \"metadata\" in (outer as Record<string, unknown>)\n ) {\n return (outer as Record<string, unknown>).data as Record<string, unknown>;\n }\n return outer as Record<string, unknown>;\n })\n .catch((err: unknown) => {\n throw new Error(`could not read Vault path \"${path}\": ${errorMessage(err)}`);\n }),\n );\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const data = await this.getData(request.path);\n\n if (!request.field) {\n const keys = Object.keys(data);\n // A single-key secret with no #field requested resolves to that\n // key's raw value directly (e.g. Vault's common { value: \"...\" }\n // convention); anything else is returned as a JSON blob.\n if (keys.length === 1) {\n const only = data[keys[0] as string];\n return typeof only === \"string\" ? only : JSON.stringify(only);\n }\n return JSON.stringify(data);\n }\n\n return extractField(JSON.stringify(data), request.field, {\n provider: this.name,\n path: request.path,\n });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.getClient().health();\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n BaseSecretProvider,\n type ProviderHealth,\n type SecretFetchRequest,\n extractField,\n} from \"./base.js\";\n\nconst DEFAULT_FILENAME = \".secrefs.local.json\";\n\nexport interface LocalProviderOptions {\n /** Overrides the file path. Defaults to $SECREFS_LOCAL_FILE or ./.secrefs.local.json */\n filePath?: string;\n /** Keep the parsed file in memory instead of re-reading per fetch.\n * Off by default so edits take effect immediately. */\n cacheFile?: boolean;\n}\n\n/**\n * Reads secrets from a gitignored, developer-local JSON file. Intended for\n * local development only - never point this at anything checked into\n * version control. Each top-level key is a secret path; its value is either\n * a plain string (returned as-is when no `#field` is requested) or an\n * object (JSON-stringified, then field-extracted as needed).\n *\n * Example `.secrefs.local.json`:\n * ```json\n * { \"mock-db\": { \"password\": \"hunter2\", \"user\": \"postgres\" } }\n * ```\n */\nexport class LocalProvider extends BaseSecretProvider {\n readonly name = \"local\";\n\n private readonly filePath: string;\n /** Re-read on every fetch. The file is local and tiny, and caching\n * it meant editing it mid-session silently did nothing. */\n private cache: Record<string, unknown> | null = null;\n private readonly cacheFile: boolean;\n\n constructor(options: LocalProviderOptions = {}) {\n super();\n this.filePath =\n options.filePath ??\n process.env.SECREFS_LOCAL_FILE ??\n path.join(process.cwd(), DEFAULT_FILENAME);\n this.cacheFile = options.cacheFile ?? false;\n }\n\n private async load(): Promise<Record<string, unknown>> {\n if (this.cache && this.cacheFile) return this.cache;\n\n let raw: string;\n try {\n raw = await readFile(this.filePath, \"utf8\");\n } catch (err) {\n throw new Error(\n `[local] could not read local secrets file at \"${this.filePath}\": ${\n err instanceof Error ? err.message : String(err)\n }. This file is gitignored by convention - see .secrefs.local.json in .gitignore.`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `[local] \"${this.filePath}\" is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`[local] \"${this.filePath}\" must contain a top-level JSON object`);\n }\n\n this.cache = parsed as Record<string, unknown>;\n return this.cache;\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n const data = await this.load();\n if (!(request.path in data)) {\n throw new Error(`[local] no entry for path \"${request.path}\" in ${this.filePath}`);\n }\n\n const entry = data[request.path];\n const raw = typeof entry === \"string\" ? entry : JSON.stringify(entry);\n return extractField(raw, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.load();\n return { provider: this.name, ok: true, message: this.filePath };\n } catch (err) {\n return {\n provider: this.name,\n ok: false,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import { BitwardenClient } from \"@bitwarden/sdk-napi\";\nimport {\n BaseSecretProvider,\n errorMessage,\n extractField,\n type ProviderHealth,\n type SecretFetchRequest,\n} from \"./base.js\";\nimport { ControlPlaneClient, type ControlPlaneCredentialSource } from \"../controlPlaneClient.js\";\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The subset of `@bitwarden/sdk-napi`'s `BitwardenClient` this provider\n * calls - kept narrow so tests can inject a plain mock object instead of\n * a real client, the same pattern `VaultProvider`/`AwsSecretsManagerProvider`\n * use for their own SDK clients. */\nexport interface BitwardenClientLike {\n auth(): { loginAccessToken(accessToken: string, stateFile?: string): Promise<void> };\n secrets(): {\n get(id: string): Promise<{ value: string }>;\n list(organizationId: string): Promise<{ data: { id: string; key: string }[] }>;\n };\n}\n\nexport interface BitwardenProviderOptions {\n /** Defaults to $BWS_ACCESS_TOKEN. Ignored if `controlPlane` is set. */\n accessToken?: string;\n /** Required only to resolve a `path` given as a secret *name* rather\n * than its UUID (see class docs). Defaults to $BWS_ORGANIZATION_ID.\n * Ignored if `controlPlane` is set - the control plane's distributed\n * credential supplies this instead. */\n organizationId?: string;\n /** Self-hosted instance override. Defaults to $BWS_API_URL. */\n apiUrl?: string;\n /** Self-hosted instance override. Defaults to $BWS_IDENTITY_URL. */\n identityUrl?: string;\n /**\n * Opt-in path to an encrypted session-state file the SDK can reuse\n * across calls to reduce auth rate-limiting (Bitwarden's own docs\n * describe this file's contents as fully encrypted, not plaintext\n * secret material). Omitted by default - this provider re-authenticates\n * in memory each time it's constructed and writes nothing to disk\n * unless a caller opts in.\n */\n stateFile?: string;\n /**\n * Sources the access token/organizationId from a running control plane\n * (docs/control-plane-design.md §7/§10, and §8 for why Bitwarden's\n * distribution here isn't the same as AWS's per-request minting -\n * see apps/control-plane/src/providers/bitwarden.ts). Every `fetchOne`\n * call still requests a distribution for its specific `path`, so the\n * control plane's RBAC `Grant.path_pattern` is enforced per secret even\n * though the underlying Bitwarden token itself isn't scoped that\n * narrowly - \"SDK-side enforcement\" as documented on the control-plane\n * side.\n */\n controlPlane?: ControlPlaneCredentialSource;\n /** Inject a pre-configured client (primarily for testing). */\n client?: BitwardenClientLike;\n}\n\n/**\n * Bitwarden **Secrets Manager** provider (not the password vault - see\n * https://bitwarden.com/help/secrets-manager-overview/). Two structural\n * differences from `AwsSecretsManagerProvider`/`VaultProvider` worth\n * knowing before using this:\n *\n * 1. **Secrets are end-to-end encrypted.** There is no plain authenticated\n * REST call to fetch a value - the official SDK derives a decryption\n * key from the access token during login and decrypts client-side.\n * That's why this provider depends on `@bitwarden/sdk-napi` (a beta\n * Node-API binding maintained by Bitwarden) rather than a bare `fetch`.\n * 2. **Bitwarden addresses secrets by UUID, with no path hierarchy** the\n * way AWS/Vault secret names have. `path` may be that UUID directly, or\n * - if `organizationId` is configured (ambient mode) or supplied by the\n * control plane (control-plane mode) - a human-readable secret *name*\n * (Bitwarden's \"key\" field), resolved via one cached `secrets().list()`\n * call. With neither, only UUID paths work.\n */\nexport class BitwardenProvider extends BaseSecretProvider {\n readonly name = \"bitwarden\";\n\n private readonly explicitClient?: BitwardenClientLike;\n private readonly ambientAccessToken?: string;\n private readonly ambientOrganizationId?: string;\n private readonly apiUrl?: string;\n private readonly identityUrl?: string;\n private readonly stateFile?: string;\n private readonly controlPlane?: ControlPlaneCredentialSource;\n private readonly controlPlaneClient?: ControlPlaneClient;\n\n private client: BitwardenClientLike | null = null;\n private loggedInAccessToken: string | null = null;\n private loggedIn: Promise<void> | null = null;\n private organizationId: string | undefined;\n /** Secret name -> id, populated by one `list()` call the first time a\n * non-UUID path is requested. Invalidated if `organizationId` ever\n * changes (control-plane mode, defensively - static in practice). */\n private nameToId: Promise<Map<string, string>> | null = null;\n\n constructor(options: BitwardenProviderOptions = {}) {\n super();\n this.explicitClient = options.client;\n this.apiUrl = options.apiUrl ?? process.env.BWS_API_URL;\n this.identityUrl = options.identityUrl ?? process.env.BWS_IDENTITY_URL;\n this.stateFile = options.stateFile;\n this.controlPlane = options.controlPlane;\n\n if (this.controlPlane) {\n this.controlPlaneClient =\n this.controlPlane.client ??\n new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });\n } else {\n this.ambientAccessToken = options.accessToken ?? process.env.BWS_ACCESS_TOKEN;\n this.ambientOrganizationId = options.organizationId ?? process.env.BWS_ORGANIZATION_ID;\n this.organizationId = this.ambientOrganizationId;\n }\n }\n\n private getClient(): BitwardenClientLike {\n if (this.explicitClient) return this.explicitClient;\n if (this.client) return this.client;\n this.client = new BitwardenClient({\n apiUrl: this.apiUrl,\n identityUrl: this.identityUrl,\n }) as unknown as BitwardenClientLike;\n return this.client;\n }\n\n private async loginWith(accessToken: string, organizationId: string | undefined): Promise<void> {\n if (organizationId !== this.organizationId) {\n this.nameToId = null; // stale cache keyed to a now-superseded org\n this.organizationId = organizationId;\n }\n if (this.loggedInAccessToken === accessToken && this.loggedIn) return this.loggedIn;\n\n this.loggedInAccessToken = accessToken;\n this.loggedIn = this.getClient()\n .auth()\n .loginAccessToken(accessToken, this.stateFile)\n .catch((err: unknown) => {\n this.loggedIn = null;\n this.loggedInAccessToken = null;\n throw new Error(`could not authenticate with the given access token: ${errorMessage(err)}`);\n });\n return this.loggedIn;\n }\n\n /** Ensures a session exists for `path`. Ambient mode logs in once\n * (memoized) with the ambient token; control-plane mode requests a\n * distribution for this specific `path` every call - see the\n * `controlPlane` option's docs for why that RBAC check has to be\n * per-path even though the token it returns doesn't vary. */\n private async ensureLoggedInFor(path: string): Promise<void> {\n if (!this.controlPlane) {\n if (!this.ambientAccessToken) {\n throw new Error(\"BWS_ACCESS_TOKEN is not set (required for sec://bitwarden/... references)\");\n }\n return this.loginWith(this.ambientAccessToken, this.ambientOrganizationId);\n }\n\n const minted = await this.controlPlaneClient!.mintCredential(this.controlPlane.alias, path);\n if (minted.provider !== \"bitwarden\") {\n throw new Error(\n `control plane returned a \"${minted.provider}\" credential for alias \"${this.controlPlane.alias}\", ` +\n `expected \"bitwarden\"`,\n );\n }\n return this.loginWith(minted.credentials.accessToken, minted.credentials.organizationId);\n }\n\n /** Assumes `ensureLoggedInFor(path)` has already run for this exact\n * `path` - callers always do that first, so `this.organizationId` is\n * already whatever this path's session resolved to. */\n private async resolveSecretId(path: string): Promise<string> {\n if (UUID_PATTERN.test(path)) return path;\n\n if (!this.organizationId) {\n throw new Error(\n `\"${path}\" is not a secret UUID, and no organizationId is available to look up a secret by name ` +\n `(set BWS_ORGANIZATION_ID, use the UUID directly, or - in control-plane mode - the distributed ` +\n `credential didn't include one)`,\n );\n }\n\n if (!this.nameToId) {\n const organizationId = this.organizationId;\n this.nameToId = (async () => {\n const { data } = await this.getClient().secrets().list(organizationId);\n return new Map(data.map((s) => [s.key, s.id]));\n })();\n }\n\n const map = await this.nameToId;\n const id = map.get(path);\n if (!id) {\n throw new Error(`no secret named \"${path}\" found in organization \"${this.organizationId}\"`);\n }\n return id;\n }\n\n async fetchOne(request: SecretFetchRequest): Promise<string> {\n let id: string;\n let secret: { value: string };\n try {\n // One ensureLoggedInFor per fetchOne - in control-plane mode this is\n // the one mint/RBAC-gate call for this path; resolveSecretId below\n // relies on it having already set this.organizationId.\n await this.ensureLoggedInFor(request.path);\n id = await this.resolveSecretId(request.path);\n secret = await this.getClient().secrets().get(id);\n } catch (err) {\n throw new Error(`could not fetch secret \"${request.path}\": ${errorMessage(err)}`);\n }\n return extractField(secret.value, request.field, { provider: this.name, path: request.path });\n }\n\n async healthCheck(): Promise<ProviderHealth> {\n try {\n await this.ensureLoggedInFor(\"__secrefs_health_check__\");\n return { provider: this.name, ok: true };\n } catch (err) {\n return { provider: this.name, ok: false, message: errorMessage(err) };\n }\n }\n}\n","/**\n * URI parser for SecRefs' `sec://` reference format:\n *\n * sec://<provider-alias>/<secret-path-or-id>[#<json-field>]\n *\n * sec://aws/prod/db#password\n * sec://vault/secret/data/stripe#key\n * sec://local/mock-db#password\n *\n * The provider alias is a bare identifier (letters/digits/`-`/`_`), the path\n * is opaque to this parser (providers interpret it however their backend\n * needs), and the optional `#field` fragment supports dot-notation for\n * traversing nested JSON secrets (e.g. `#nested.value`).\n */\n\nconst SEC_REF_PATTERN = /^sec:\\/\\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\\/([^\\s#]+)(?:#([^\\s#]+))?$/;\n\nexport interface ParsedSecretRef {\n /** The original, unmodified reference string. */\n raw: string;\n /** Lowercased provider alias, e.g. \"aws\", \"vault\", \"local\". */\n provider: string;\n /** The secret path/id as understood by the provider. */\n path: string;\n /** Optional dot-notation field to extract from a JSON secret. */\n field?: string;\n}\n\nexport class SecRefParseError extends Error {\n constructor(\n public readonly raw: string,\n public readonly reason: string,\n ) {\n super(`Invalid secret reference \"${raw}\": ${reason}`);\n this.name = \"SecRefParseError\";\n }\n}\n\n/** True if `value` is a string that looks like a `sec://` reference at all. */\nexport function isSecretRef(value: unknown): value is string {\n return typeof value === \"string\" && value.startsWith(\"sec://\");\n}\n\n/**\n * Parses a `sec://` reference string. Throws {@link SecRefParseError} if the\n * value isn't a string, doesn't start with `sec://`, or doesn't match the\n * full `<provider>/<path>[#field]` shape.\n */\nexport function parseSecretRef(raw: unknown): ParsedSecretRef {\n if (typeof raw !== \"string\") {\n throw new SecRefParseError(String(raw), \"reference must be a string\");\n }\n\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"sec://\")) {\n throw new SecRefParseError(raw, 'must start with \"sec://\"');\n }\n\n const match = SEC_REF_PATTERN.exec(trimmed);\n if (!match) {\n throw new SecRefParseError(\n raw,\n \"does not match sec://<provider>/<path>[#field] format\",\n );\n }\n\n const [, provider, path, field] = match;\n if (!provider) {\n throw new SecRefParseError(raw, \"missing provider alias\");\n }\n if (!path) {\n throw new SecRefParseError(raw, \"missing secret path\");\n }\n\n return {\n raw,\n provider: provider.toLowerCase(),\n path,\n field: field || undefined,\n };\n}\n\n/** Best-effort parse that returns `null` instead of throwing. */\nexport function tryParseSecretRef(raw: unknown): ParsedSecretRef | null {\n try {\n return parseSecretRef(raw);\n } catch {\n return null;\n }\n}\n","import { type ParsedSecretRef, isSecretRef, parseSecretRef } from \"./parser.js\";\nimport {\n errorMessage,\n SecretFetchError,\n type ISecretProvider,\n type SecretFetchRequest,\n} from \"./providers/base.js\";\nimport type { SecretErrorKind } from \"./providers/errors.js\";\n\nexport type ProviderRegistry = Record<string, ISecretProvider>;\n\nexport interface ExpandOptions {\n providers: ProviderRegistry;\n /**\n * When true (default), a value that starts with `sec://` but fails to\n * parse throws immediately. When false, such values are left untouched.\n * This only affects syntactically malformed references - unknown\n * providers and provider-side fetch failures always surface as errors,\n * aggregated in a {@link SecRefsResolutionError}.\n */\n strict?: boolean;\n}\n\nexport interface ResolutionFailure {\n /** The env var / map key the reference was assigned to. */\n key: string;\n /** The original `sec://` string. */\n ref: string;\n message: string;\n /** Whose problem this is. `undefined` when the failure came from\n * somewhere that doesn't classify (a malformed reference, say). */\n kind?: SecretErrorKind;\n /** Provider alias the reference named, for grouping auth failures. */\n provider?: string;\n /** What to run to fix it, for auth failures. */\n remedy?: string;\n}\n\n/**\n * Renders failures the way they should be read.\n *\n * One expired credential produces one failure per reference, and listing\n * them individually - `DB_PASSWORD: could not fetch...`, four times -\n * reads as four broken secrets and sends people to check their secrets.\n * Auth failures are therefore collapsed per provider, stated as an\n * environment problem, and given the command that fixes them. Path\n * failures stay itemised, because there the reference really is the\n * thing at fault.\n */\nfunction formatFailures(errors: ResolutionFailure[]): string {\n const auth = errors.filter((e) => e.kind === \"auth\");\n const rest = errors.filter((e) => e.kind !== \"auth\");\n const lines: string[] = [];\n\n for (const provider of [...new Set(auth.map((e) => e.provider ?? \"unknown\"))]) {\n const group = auth.filter((e) => (e.provider ?? \"unknown\") === provider);\n lines.push(`Cannot authenticate to provider \"${provider}\".`);\n lines.push(` ${group[0]!.message}`);\n const remedy = group.find((e) => e.remedy)?.remedy;\n if (remedy) lines.push(` ${remedy}`);\n lines.push(` Not resolved: ${group.map((e) => e.key).join(\", \")}`);\n }\n\n if (rest.length > 0) {\n if (lines.length > 0) lines.push(\"\");\n lines.push(`Failed to resolve ${rest.length} secret reference(s):`);\n for (const e of rest) lines.push(` - ${e.key}: ${e.ref} -> ${e.message}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport class SecRefsResolutionError extends Error {\n constructor(public readonly errors: ResolutionFailure[]) {\n super(formatFailures(errors));\n this.name = \"SecRefsResolutionError\";\n }\n\n /** True when every failure was an environment/auth problem, so a caller\n * can tell \"your credentials lapsed\" from \"your references are wrong\". */\n get isAuthOnly(): boolean {\n return this.errors.length > 0 && this.errors.every((e) => e.kind === \"auth\");\n }\n}\n\nexport interface CheckResult {\n key: string;\n ref: string;\n provider: string;\n ok: boolean;\n /** Present only when ok is false. Never contains the secret value. */\n message?: string;\n}\n\nasync function resolveOne(\n ref: ParsedSecretRef,\n providers: ProviderRegistry,\n): Promise<string> {\n const provider = providers[ref.provider];\n if (!provider) {\n const available = Object.keys(providers).join(\", \") || \"none configured\";\n throw new Error(`unknown provider \"${ref.provider}\" (available: ${available})`);\n }\n const request: SecretFetchRequest = { path: ref.path, field: ref.field };\n return provider.fetchOne(request);\n}\n\n/**\n * Expands every `sec://` value in a plain key/value map, resolving all\n * references concurrently via `Promise.allSettled`. Non-reference values\n * pass through untouched. Never writes anything to disk - the caller\n * decides what to do with the returned map (assign to `process.env`,\n * template into a string, etc).\n *\n * Throws {@link SecRefsResolutionError} aggregating every failed\n * reference if any fail to resolve.\n */\nexport async function expandKeyValueMap(\n input: Record<string, string | undefined>,\n options: ExpandOptions,\n): Promise<Record<string, string>> {\n const strict = options.strict ?? true;\n const output: Record<string, string> = {};\n const pending: { key: string; ref: ParsedSecretRef }[] = [];\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined) continue;\n if (!isSecretRef(value)) {\n output[key] = value;\n continue;\n }\n try {\n pending.push({ key, ref: parseSecretRef(value) });\n } catch (err) {\n if (strict) throw err;\n output[key] = value;\n }\n }\n\n if (pending.length === 0) {\n return output;\n }\n\n const settled = await Promise.allSettled(\n pending.map(({ ref }) => resolveOne(ref, options.providers)),\n );\n\n const errors: ResolutionFailure[] = [];\n settled.forEach((result, i) => {\n const { key, ref } = pending[i] as { key: string; ref: ParsedSecretRef };\n if (result.status === \"fulfilled\") {\n output[key] = result.value;\n } else {\n const reason = result.reason;\n errors.push({\n key,\n ref: ref.raw,\n message: errorMessage(reason),\n kind: reason instanceof SecretFetchError ? reason.kind : undefined,\n provider: reason instanceof SecretFetchError ? reason.provider : ref.provider,\n remedy: reason instanceof SecretFetchError ? reason.remedy : undefined,\n });\n }\n });\n\n if (errors.length > 0) {\n throw new SecRefsResolutionError(errors);\n }\n\n return output;\n}\n\n/**\n * Expands `sec://` values found in `process.env`, mutating it in place.\n * Returns the list of env var names that were rewritten.\n */\nexport async function expandProcessEnv(options: ExpandOptions): Promise<string[]> {\n const resolved = await expandKeyValueMap(process.env, options);\n const changedKeys: string[] = [];\n for (const [key, value] of Object.entries(resolved)) {\n if (process.env[key] !== value) {\n process.env[key] = value;\n changedKeys.push(key);\n }\n }\n return changedKeys;\n}\n\n/**\n * Dry-run validation: resolves every `sec://` reference found in `input`\n * but reports only ok/failure per reference - the secret values themselves\n * are never returned or logged. Used by `secrefs check`.\n */\nexport async function checkReferences(\n input: Record<string, string | undefined>,\n options: ExpandOptions,\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n const parsedEntries: { key: string; ref: ParsedSecretRef }[] = [];\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || !isSecretRef(value)) continue;\n try {\n parsedEntries.push({ key, ref: parseSecretRef(value) });\n } catch (err) {\n results.push({ key, ref: value, provider: \"unknown\", ok: false, message: errorMessage(err) });\n }\n }\n\n const settled = await Promise.allSettled(\n parsedEntries.map(({ ref }) => resolveOne(ref, options.providers)),\n );\n\n settled.forEach((result, i) => {\n const { key, ref } = parsedEntries[i] as { key: string; ref: ParsedSecretRef };\n results.push({\n key,\n ref: ref.raw,\n provider: ref.provider,\n ok: result.status === \"fulfilled\",\n message: result.status === \"rejected\" ? errorMessage(result.reason) : undefined,\n });\n });\n\n return results;\n}\n","import { parse as parseDotenv } from \"dotenv\";\n\n// Matches an *unquoted* `KEY=sec://...` assignment, capturing the rest of\n// the line verbatim as the value.\nconst UNQUOTED_SEC_REF_LINE =\n /^[ \\t]*(?:export[ \\t]+)?([A-Za-z_][A-Za-z0-9_]*)[ \\t]*=[ \\t]*(sec:\\/\\/\\S.*)$/;\n\n/**\n * dotenv treats `#` as a start-of-comment marker even mid-value (unless\n * the value is quoted), which silently truncates the `#field` fragment\n * off an unquoted `sec://provider/path#field` reference - the exact\n * format SecRefs itself uses. Rather than require every `sec://` value in\n * `.env` to be quoted (an easy thing to forget, with no error if you do),\n * this re-scans the raw file for unquoted `sec://` assignments and\n * restores the value dotenv would otherwise clip.\n *\n * Note: because of this, an unquoted `sec://` value can't have a\n * trailing inline comment on the same line - put comments on their own\n * line instead. Quoted values (`KEY=\"sec://...#field\"`) are unaffected\n * and already handled correctly by dotenv itself.\n */\nexport function recoverTruncatedSecRefs(\n rawText: string,\n parsed: Record<string, string>,\n): Record<string, string> {\n const result = { ...parsed };\n for (const line of rawText.split(/\\r?\\n/)) {\n const match = UNQUOTED_SEC_REF_LINE.exec(line);\n if (!match) continue;\n const [, key, value] = match;\n if (!key || value === undefined) continue;\n result[key] = value.trimEnd();\n }\n return result;\n}\n\n/** Parses a `.env` file's contents, correctly preserving `sec://...#field` fragments. */\nexport function parseEnvFileText(rawText: string): Record<string, string> {\n const parsed = parseDotenv(rawText);\n return recoverTruncatedSecRefs(rawText, parsed);\n}\n","import { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { AwsSecretsManagerProvider } from \"./providers/aws.js\";\nimport { BitwardenProvider } from \"./providers/bitwarden.js\";\nimport { LocalProvider } from \"./providers/local.js\";\nimport { VaultProvider } from \"./providers/vault.js\";\nimport type { ProviderRegistry } from \"./resolver.js\";\n\n/**\n * Project configuration: `secrefs.config.json`.\n *\n * The reference format has always supported arbitrary aliases -\n * `ProviderRegistry` is a plain `Record<string, ISecretProvider>` - but\n * only library callers could register them. The CLI was stuck with four\n * hardcoded names, so `secrefs run` could reach exactly one AWS account\n * and one Bitwarden vault. This closes that gap.\n *\n * **This file never holds a secret.** Every credential is referenced by\n * the name of the environment variable that carries it, or by an AWS\n * profile name. That is the whole design constraint: a config file that\n * could hold a token would recreate the `.env` problem one level up, in\n * the tool built to solve it. `secrefs.config.json` is meant to be\n * committed, and nothing in this parser will read a literal credential\n * even if someone puts one there.\n */\n\nexport const CONFIG_FILENAME = \"secrefs.config.json\";\n\nexport interface AwsAliasConfig {\n type: \"aws\";\n /** Named profile from ~/.aws/config. Uses the ambient chain if absent. */\n profile?: string;\n region?: string;\n /** Milliseconds a fetched value may be reused. Default 0 (re-fetch). */\n cacheTtlMs?: number;\n /** Milliseconds a stale value may answer a *transient* failure. */\n staleGraceMs?: number;\n}\n\nexport interface BitwardenAliasConfig {\n type: \"bitwarden\";\n /** Name of the env var holding the machine account token. Never the\n * token. Defaults to BWS_ACCESS_TOKEN. */\n tokenEnv?: string;\n /** Name of the env var holding the organization id. */\n organizationIdEnv?: string;\n apiUrl?: string;\n identityUrl?: string;\n}\n\nexport interface VaultAliasConfig {\n type: \"vault\";\n /** Name of the env var holding the Vault token. Defaults to VAULT_TOKEN. */\n tokenEnv?: string;\n addr?: string;\n}\n\nexport interface LocalAliasConfig {\n type: \"local\";\n /** Path to the gitignored JSON file, relative to the config file. */\n file?: string;\n}\n\nexport type AliasConfig =\n | AwsAliasConfig\n | BitwardenAliasConfig\n | VaultAliasConfig\n | LocalAliasConfig;\n\nexport interface SecRefsConfig {\n providers: Record<string, AliasConfig>;\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\n/** Field names that would mean a literal credential in the file. Rejected\n * loudly rather than ignored: someone who wrote `\"token\": \"...\"` believes\n * it is being used, and silently not using it is worse than refusing. */\nconst FORBIDDEN_KEYS = new Set([\n \"token\",\n \"accessToken\",\n \"access_token\",\n \"secret\",\n \"secretKey\",\n \"secretAccessKey\",\n \"password\",\n \"apiKey\",\n \"credential\",\n \"credentials\",\n]);\n\nfunction assertNoInlineSecrets(alias: string, config: Record<string, unknown>): void {\n for (const key of Object.keys(config)) {\n if (FORBIDDEN_KEYS.has(key)) {\n throw new ConfigError(\n `${CONFIG_FILENAME}: provider \"${alias}\" sets \"${key}\". This file is meant to be ` +\n `committed and must never contain a credential. Reference the environment variable ` +\n `that holds it instead - e.g. \"tokenEnv\": \"BWS_ACCESS_TOKEN\".`,\n );\n }\n }\n}\n\n/** Reads an env var named by config, failing with a message that names\n * both the alias and the variable - \"BWS_TOKEN_ENG is not set\" is\n * actionable in a way that \"missing credentials\" is not. */\nfunction requireEnv(alias: string, varName: string, env: NodeJS.ProcessEnv): string {\n const value = env[varName];\n if (!value) {\n throw new ConfigError(\n `${CONFIG_FILENAME}: provider \"${alias}\" expects the credential in ${varName}, ` +\n `but that environment variable is not set.`,\n );\n }\n return value;\n}\n\nexport function parseConfig(raw: string, source = CONFIG_FILENAME): SecRefsConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new ConfigError(`${source} is not valid JSON: ${(err as Error).message}`);\n }\n\n // Array.isArray as well as the typeof check: an array is an object to\n // typeof, so `[]` would otherwise slip through and fail later with a\n // message about a missing \"providers\" key, which is not what is wrong.\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new ConfigError(`${source} must contain a JSON object.`);\n }\n\n const providers = (parsed as { providers?: unknown }).providers;\n if (typeof providers !== \"object\" || providers === null) {\n throw new ConfigError(`${source} must have a \"providers\" object.`);\n }\n\n for (const [alias, value] of Object.entries(providers as Record<string, unknown>)) {\n if (typeof value !== \"object\" || value === null) {\n throw new ConfigError(`${source}: provider \"${alias}\" must be an object.`);\n }\n const type = (value as { type?: unknown }).type;\n if (type !== \"aws\" && type !== \"bitwarden\" && type !== \"vault\" && type !== \"local\") {\n throw new ConfigError(\n `${source}: provider \"${alias}\" has type ${JSON.stringify(type)}; ` +\n `expected one of \"aws\", \"bitwarden\", \"vault\", \"local\".`,\n );\n }\n assertNoInlineSecrets(alias, value as Record<string, unknown>);\n }\n\n return parsed as SecRefsConfig;\n}\n\n/**\n * Builds a provider registry from parsed config. Aliases entirely replace\n * the built-in defaults rather than merging with them: a config that\n * declares `aws-prod` and `aws-staging` almost certainly does *not* want\n * a third, differently-configured `aws` quietly still working, because\n * that is how a reference ends up resolving against the wrong account.\n */\nexport function buildProviders(\n config: SecRefsConfig,\n options: { configDir?: string; env?: NodeJS.ProcessEnv } = {},\n): ProviderRegistry {\n const env = options.env ?? process.env;\n const configDir = options.configDir ?? process.cwd();\n const registry: ProviderRegistry = {};\n\n for (const [alias, entry] of Object.entries(config.providers)) {\n switch (entry.type) {\n case \"aws\":\n registry[alias] = new AwsSecretsManagerProvider({\n region: entry.region,\n cacheTtlMs: entry.cacheTtlMs,\n staleGraceMs: entry.staleGraceMs,\n profile: entry.profile,\n });\n break;\n case \"bitwarden\":\n registry[alias] = new BitwardenProvider({\n accessToken: requireEnv(alias, entry.tokenEnv ?? \"BWS_ACCESS_TOKEN\", env),\n organizationId: entry.organizationIdEnv\n ? requireEnv(alias, entry.organizationIdEnv, env)\n : env.BWS_ORGANIZATION_ID,\n apiUrl: entry.apiUrl,\n identityUrl: entry.identityUrl,\n });\n break;\n case \"vault\":\n registry[alias] = new VaultProvider({\n endpoint: entry.addr ?? env.VAULT_ADDR,\n token: requireEnv(alias, entry.tokenEnv ?? \"VAULT_TOKEN\", env),\n });\n break;\n case \"local\":\n registry[alias] = new LocalProvider({\n filePath: entry.file ? resolve(configDir, entry.file) : undefined,\n });\n break;\n }\n }\n\n return registry;\n}\n\n/**\n * Loads `secrefs.config.json` from `dir`, or from the nearest ancestor\n * that has one - so `secrefs run` works from a subdirectory of a repo the\n * way git and every other project tool does. Returns undefined when no\n * config exists anywhere up the tree, which is the common case and not an\n * error: the built-in aliases are used instead.\n */\nexport function loadConfigFrom(\n dir: string = process.cwd(),\n): { config: SecRefsConfig; path: string } | undefined {\n let current = resolve(dir);\n for (;;) {\n const candidate = resolve(current, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = readFileSync(candidate, \"utf8\");\n } catch {\n const parent = dirname(current);\n if (parent === current) return undefined; // reached the filesystem root\n current = parent;\n continue;\n }\n return { config: parseConfig(raw, candidate), path: candidate };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oCAIO;;;AC4BP,IAAM,aAAa,oBAAI,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,OAAO,KAAsB;AACpC,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAM,IAAI;AACV,aAAW,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;AAClD,QAAI,OAAO,cAAc,YAAY,UAAW,QAAO;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,SAAS,KAAkC;AAClD,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAM,OAAQ,IAAoD;AAClE,MAAI,OAAO,MAAM,mBAAmB,SAAU,QAAO,KAAK;AAC1D,QAAM,SAAU,IAAmD,UAAW,IAAiC;AAC/G,SAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AASO,SAAS,cAAc,KAA+B;AAC3D,QAAM,OAAO,OAAO,GAAG;AACvB,MAAI,WAAW,IAAI,IAAI,EAAG,QAAO;AACjC,MAAI,gBAAgB,IAAI,IAAI,EAAG,QAAO;AACtC,MAAI,aAAa,IAAI,IAAI,EAAG,QAAO;AACnC,MAAI,gBAAgB,IAAI,IAAI,EAAG,QAAO;AAEtC,QAAM,SAAS,SAAS,GAAG;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO;AAC7C,MAAI,WAAW,UAAa,UAAU,IAAK,QAAO;AAElD,QAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,OAAO,EAAE,GAAG,YAAY;AAKrF,MAAI,eAAe,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAC5D,MAAI,oBAAoB,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAEjE,SAAO;AACT;AAOO,SAAS,gBAAgB,MAAgC;AAC9D,SAAO,SAAS;AAClB;AAOO,SAAS,UAAU,MAAuB,UAAkB,KAAmC;AACpG,MAAI,SAAS,OAAQ,QAAO;AAI5B,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,MAAI,eAAe,KAAK,OAAO,GAAG;AAChC,UAAM,UAAU,QAAQ,IAAI;AAC5B,WAAO,UACH,gCAAgC,OAAO,KACvC;AAAA,EACN;AAEA,UAAQ,UAAU;AAAA,IAChB,KAAK,OAAO;AACV,YAAM,UAAU,QAAQ,IAAI;AAC5B,aAAO,UACH,sCAAsC,OAAO,oDAAoD,OAAO,KACxG;AAAA,IACN;AAAA,IACA,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;AC7IO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAQ1C,YACkB,UACAA,OACA,OAChB;AACA,UAAM,OAAO,cAAc,KAAK;AAIhC;AAAA,MACE,SAAS,SACL,IAAI,QAAQ,0BAA0B,aAAa,KAAK,CAAC,KACzD,IAAI,QAAQ,gCAAgCA,KAAI,MAAM,aAAa,KAAK,CAAC;AAAA,IAC/E;AAZgB;AACA,gBAAAA;AACA;AAWhB,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS,UAAU,MAAM,UAAU,KAAK;AAAA,EAC/C;AAAA,EAhBkB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAPT;AAAA;AAAA,EAEA;AAoBX;AAEO,IAAe,qBAAf,MAA6D;AAAA,EAKlE,MAAM,WAAW,UAAmD;AAClE,UAAM,UAAU,MAAM,QAAQ,WAAW,SAAS,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC;AAC9E,WAAO,QAAQ,IAAI,CAAC,QAAQ,MAAM;AAChC,YAAM,UAAU,SAAS,CAAC;AAC1B,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,OAAO;AAAA,MAChB;AACA,YAAM,IAAI,iBAAiB,KAAK,MAAM,SAAS,QAAQ,aAAa,OAAO,MAAM;AAAA,IACnF,CAAC;AAAA,EACH;AAGF;AAEO,SAAS,aAAa,KAAsB;AACjD,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,SAAO,OAAO,GAAG;AACnB;AAQO,SAAS,aACd,KACA,OACA,SACQ;AACR,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,IAAI,QAAQ,QAAQ,gBAAgB,QAAQ,IAAI,wCAAwC,KAAK;AAAA,IAC/F;AAAA,EACF;AAEA,MAAI,UAAmB;AACvB,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AACnC,QAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;AACnD,YAAM,IAAI;AAAA,QACR,IAAI,QAAQ,QAAQ,YAAY,KAAK,6BAA6B,QAAQ,IAAI;AAAA,MAChF;AAAA,IACF;AACA,cAAW,QAAoC,IAAI;AAAA,EACrD;AAEA,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI;AAAA,MACR,IAAI,QAAQ,QAAQ,YAAY,KAAK,6BAA6B,QAAQ,IAAI;AAAA,IAChF;AAAA,EACF;AAEA,SAAO,OAAO,YAAY,WAAW,KAAK,UAAU,OAAO,IAAI,OAAO,OAAO;AAC/E;;;AF5HA,kCAAsC;;;AGqD/B,IAAM,WAAN,MAAkB;AAAA;AAAA,EAEN,UAAU,oBAAI,IAAsB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpC,WAAW,oBAAI,IAAwB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,UAA2B,CAAC,GAAG;AACzC,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,kBAAkB,QAAQ,oBAAoB,MAAM;AACzD,SAAK,UAAU,QAAQ;AACvB,SAAK,MAAM,QAAQ,OAAO,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MAAM,KAAa,MAAoC;AAE3D,UAAM,kBAAkB,KAAK,SAAS,IAAI,GAAG;AAC7C,QAAI,gBAAiB,QAAO;AAE5B,UAAM,SAAS,KAAK,QAAQ,IAAI,GAAG;AACnC,QAAI,UAAU,KAAK,QAAQ,KAAK,KAAK,IAAI,IAAI,OAAO,WAAW,KAAK,OAAO;AACzE,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,UAAU,KAAK;AACrB,SAAK,SAAS,IAAI,KAAK,OAAO;AAE9B,QAAI;AACF,YAAM,QAAQ,MAAM;AAKpB,UAAI,KAAK,QAAQ,KAAK,KAAK,eAAe,GAAG;AAC3C,aAAK,QAAQ,IAAI,KAAK,EAAE,OAAO,QAAQ,QAAQ,KAAK,GAAG,UAAU,KAAK,IAAI,EAAE,CAAC;AAAA,MAC/E;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,UAAI,YAAY,KAAK,eAAe,KAAK,KAAK,gBAAgB,GAAG,GAAG;AAClE,cAAM,QAAQ,KAAK,IAAI,IAAI,SAAS;AACpC,YAAI,SAAS,KAAK,cAAc;AAI9B,eAAK,UAAU,KAAK,OAAO,GAAG;AAC9B,iBAAO,SAAS;AAAA,QAClB;AAAA,MACF;AAGA,WAAK,QAAQ,OAAO,GAAG;AACvB,YAAM;AAAA,IACR,UAAE;AACA,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,QAAc;AACZ,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;;;AClFO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YACkB,QAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAoC;AAC9C,SAAK,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AACjD,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ,aAAa;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,OAAeC,OAA+C;AACjF,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,wBAAwB;AAAA,QACrE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,KAAK,KAAK,GAAG;AAAA,QACrF,MAAM,KAAK,UAAU,EAAE,OAAO,MAAAA,MAAK,CAAC;AAAA,MACtC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,oCAAoC,KAAK,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACvG;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,YAAM,IAAI;AAAA,QACR,SAAS;AAAA,QACT,KAAK,SAAS,0BAA0B,SAAS,MAAM,eAAe,KAAK,WAAWA,KAAI;AAAA,MAC5F;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B;AACF;;;AJxBO,IAAM,4BAAN,cAAwC,mBAAmB;AAAA,EACvD,OAAO;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,gBAA6C;AAAA,EACpC;AAAA,EAEjB,YAAY,UAA8B,CAAC,GAAG;AAC5C,UAAM;AACN,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AACvB,SAAK,eAAe,QAAQ;AAC5B,SAAK,WAAW,IAAI,SAAiB;AAAA,MACnC,OAAO,QAAQ;AAAA,MACf,cAAc,QAAQ;AAAA,MACtB,iBAAiB,CAAC,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,MAC5D,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,QAAI,KAAK,cAAc;AACrB,WAAK,qBACH,KAAK,aAAa,UAClB,IAAI,mBAAmB,EAAE,SAAS,KAAK,aAAa,SAAS,OAAO,KAAK,aAAa,MAAM,CAAC;AAAA,IACjG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,UAAUC,OAA6C;AACnE,QAAI,KAAK,eAAgB,QAAO,KAAK;AAErC,QAAI,KAAK,gBAAgB,KAAK,oBAAoB;AAChD,YAAM,SAAS,MAAM,KAAK,mBAAmB,eAAe,KAAK,aAAa,OAAOA,KAAI;AACzF,UAAI,OAAO,aAAa,OAAO;AAC7B,cAAM,IAAI;AAAA,UACR,6BAA6B,OAAO,QAAQ,2BAA2B,KAAK,aAAa,KAAK;AAAA,QAEhG;AAAA,MACF;AACA,aAAO,KAAK,iCAAiC,OAAO,WAAW;AAAA,IACjE;AAEA,QAAI,CAAC,KAAK,eAAe;AACvB,WAAK,gBAAgB,IAAI,mDAAqB;AAAA,QAC5C,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA,QAIb,GAAI,KAAK,UAAU,EAAE,iBAAa,mDAAsB,EAAE,SAAS,KAAK,QAAQ,CAAC,EAAE,IAAI,CAAC;AAAA,MAC1F,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,iCAAiC,aAAyD;AAChG,WAAO,IAAI,mDAAqB;AAAA,MAC9B,QAAQ,KAAK;AAAA,MACb,aAAa;AAAA,QACX,aAAa,YAAY;AAAA,QACzB,iBAAiB,YAAY;AAAA,QAC7B,cAAc,YAAY;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,OAAOA,OAA+B;AAC5C,WAAO,KAAK,SAAS,MAAMA,OAAM,YAAY;AAC3C,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,UAAUA,KAAI;AACxC,cAAM,WAAW,MAAM,OAAO,KAAK,IAAI,oDAAsB,EAAE,UAAUA,MAAK,CAAC,CAAC;AAChF,YAAI,OAAO,SAAS,iBAAiB,UAAU;AAC7C,iBAAO,SAAS;AAAA,QAClB;AACA,YAAI,SAAS,cAAc;AACzB,iBAAO,OAAO,KAAK,SAAS,YAA0B,EAAE,SAAS,MAAM;AAAA,QACzE;AACA,cAAM,IAAI,MAAM,WAAWA,KAAI,+CAA+C;AAAA,MAChF,SAAS,KAAK;AAIZ,YAAI,eAAe,iBAAkB,OAAM;AAC3C,cAAM,IAAI,iBAAiB,KAAK,MAAMA,OAAM,GAAG;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,SAA8C;AAC3D,UAAM,MAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AAC1C,WAAO,aAAa,KAAK,QAAQ,OAAO,EAAE,UAAU,KAAK,MAAM,MAAM,QAAQ,KAAK,CAAC;AAAA,EACrF;AAAA,EAEA,MAAM,cAAuC;AAC3C,QAAI;AACF,UAAI,KAAK,cAAc;AAQrB,cAAM,qBACJ,KAAK,aAAa,UAClB,IAAI,mBAAmB,EAAE,SAAS,KAAK,aAAa,SAAS,OAAO,KAAK,aAAa,MAAM,CAAC;AAC/F,YAAI;AACF,gBAAM,mBAAmB,eAAe,KAAK,aAAa,OAAO,0BAA0B;AAAA,QAC7F,SAAS,KAAK;AACZ,cAAI,eAAe,0BAA0B;AAC3C,mBAAO,EAAE,UAAU,KAAK,MAAM,IAAI,MAAM,SAAS,0BAA0B;AAAA,UAC7E;AACA,gBAAM;AAAA,QACR;AACA,eAAO,EAAE,UAAU,KAAK,MAAM,IAAI,KAAK;AAAA,MACzC;AAIA,YAAM,SAAS,MAAM,KAAK,UAAU,0BAA0B;AAC9D,YAAM,OAAO,KAAK,IAAI,iDAAmB,EAAE,YAAY,EAAE,CAAC,CAAC;AAC3D,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,KAAK;AAAA,IACzC,SAAS,KAAK;AACZ,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,OAAO,SAAS,aAAa,GAAG,EAAE;AAAA,IACtE;AAAA,EACF;AACF;;;AK3NA,wBAAyB;AAiClB,IAAM,gBAAN,cAA4B,mBAAmB;AAAA,EAC3C,OAAO;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACT,SAAiD;AAAA,EACxC;AAAA,EAEjB,YAAY,UAAgC,CAAC,GAAG;AAC9C,UAAM;AACN,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,WAAW,QAAQ,YAAY,QAAQ,IAAI;AAChD,SAAK,QAAQ,QAAQ,SAAS,QAAQ,IAAI;AAC1C,SAAK,YAAY,IAAI,SAAkC,EAAE,OAAO,QAAQ,WAAW,CAAC;AAAA,EACtF;AAAA,EAEQ,YAA6C;AACnD,QAAI,KAAK,eAAgB,QAAO,KAAK;AACrC,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AACA,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAEA,SAAK,aAAS,kBAAAC,SAAa,EAAE,UAAU,KAAK,UAAU,OAAO,KAAK,MAAM,CAAC;AACzE,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,QAAQC,OAAgD;AAC9D,WAAO,KAAK,UAAU;AAAA,MAAMA;AAAA,MAAM,MAChC,KAAK,UAAU,EACd,KAAKA,KAAI,EACT,KAAK,CAAC,aAAa;AAClB,cAAM,QAAQ,SAAS;AACvB,YAAI,UAAU,UAAa,UAAU,MAAM;AACzC,gBAAM,IAAI,MAAM,8BAA8BA,KAAI,GAAG;AAAA,QACvD;AAGA,YACE,OAAO,UAAU,YACjB,UAAW,SACX,cAAe,OACf;AACA,iBAAQ,MAAkC;AAAA,QAC5C;AACA,eAAO;AAAA,MACT,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,cAAM,IAAI,MAAM,8BAA8BA,KAAI,MAAM,aAAa,GAAG,CAAC,EAAE;AAAA,MAC7E,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,SAA8C;AAC3D,UAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,IAAI;AAE5C,QAAI,CAAC,QAAQ,OAAO;AAClB,YAAM,OAAO,OAAO,KAAK,IAAI;AAI7B,UAAI,KAAK,WAAW,GAAG;AACrB,cAAM,OAAO,KAAK,KAAK,CAAC,CAAW;AACnC,eAAO,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI;AAAA,MAC9D;AACA,aAAO,KAAK,UAAU,IAAI;AAAA,IAC5B;AAEA,WAAO,aAAa,KAAK,UAAU,IAAI,GAAG,QAAQ,OAAO;AAAA,MACvD,UAAU,KAAK;AAAA,MACf,MAAM,QAAQ;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAuC;AAC3C,QAAI;AACF,YAAM,KAAK,UAAU,EAAE,OAAO;AAC9B,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,KAAK;AAAA,IACzC,SAAS,KAAK;AACZ,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,OAAO,SAAS,aAAa,GAAG,EAAE;AAAA,IACtE;AAAA,EACF;AACF;;;ACxHA,sBAAyB;AACzB,uBAAiB;AAQjB,IAAM,mBAAmB;AAsBlB,IAAM,gBAAN,cAA4B,mBAAmB;AAAA,EAC3C,OAAO;AAAA,EAEC;AAAA;AAAA;AAAA,EAGT,QAAwC;AAAA,EAC/B;AAAA,EAEjB,YAAY,UAAgC,CAAC,GAAG;AAC9C,UAAM;AACN,SAAK,WACH,QAAQ,YACR,QAAQ,IAAI,sBACZ,iBAAAC,QAAK,KAAK,QAAQ,IAAI,GAAG,gBAAgB;AAC3C,SAAK,YAAY,QAAQ,aAAa;AAAA,EACxC;AAAA,EAEA,MAAc,OAAyC;AACrD,QAAI,KAAK,SAAS,KAAK,UAAW,QAAO,KAAK;AAE9C,QAAI;AACJ,QAAI;AACF,YAAM,UAAM,0BAAS,KAAK,UAAU,MAAM;AAAA,IAC5C,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,iDAAiD,KAAK,QAAQ,MAC5D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,YAAY,KAAK,QAAQ,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACnG;AAAA,IACF;AAEA,QAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,YAAM,IAAI,MAAM,YAAY,KAAK,QAAQ,wCAAwC;AAAA,IACnF;AAEA,SAAK,QAAQ;AACb,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAAS,SAA8C;AAC3D,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,EAAE,QAAQ,QAAQ,OAAO;AAC3B,YAAM,IAAI,MAAM,8BAA8B,QAAQ,IAAI,QAAQ,KAAK,QAAQ,EAAE;AAAA,IACnF;AAEA,UAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,UAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;AACpE,WAAO,aAAa,KAAK,QAAQ,OAAO,EAAE,UAAU,KAAK,MAAM,MAAM,QAAQ,KAAK,CAAC;AAAA,EACrF;AAAA,EAEA,MAAM,cAAuC;AAC3C,QAAI;AACF,YAAM,KAAK,KAAK;AAChB,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,MAAM,SAAS,KAAK,SAAS;AAAA,IACjE,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,UAAU,KAAK;AAAA,QACf,IAAI;AAAA,QACJ,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AACF;;;ACvGA,sBAAgC;AAUhC,IAAM,eAAe;AAqEd,IAAM,oBAAN,cAAgC,mBAAmB;AAAA,EAC/C,OAAO;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,SAAqC;AAAA,EACrC,sBAAqC;AAAA,EACrC,WAAiC;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAIA,WAAgD;AAAA,EAExD,YAAY,UAAoC,CAAC,GAAG;AAClD,UAAM;AACN,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,SAAS,QAAQ,UAAU,QAAQ,IAAI;AAC5C,SAAK,cAAc,QAAQ,eAAe,QAAQ,IAAI;AACtD,SAAK,YAAY,QAAQ;AACzB,SAAK,eAAe,QAAQ;AAE5B,QAAI,KAAK,cAAc;AACrB,WAAK,qBACH,KAAK,aAAa,UAClB,IAAI,mBAAmB,EAAE,SAAS,KAAK,aAAa,SAAS,OAAO,KAAK,aAAa,MAAM,CAAC;AAAA,IACjG,OAAO;AACL,WAAK,qBAAqB,QAAQ,eAAe,QAAQ,IAAI;AAC7D,WAAK,wBAAwB,QAAQ,kBAAkB,QAAQ,IAAI;AACnE,WAAK,iBAAiB,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,YAAiC;AACvC,QAAI,KAAK,eAAgB,QAAO,KAAK;AACrC,QAAI,KAAK,OAAQ,QAAO,KAAK;AAC7B,SAAK,SAAS,IAAI,gCAAgB;AAAA,MAChC,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK;AAAA,IACpB,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAAU,aAAqB,gBAAmD;AAC9F,QAAI,mBAAmB,KAAK,gBAAgB;AAC1C,WAAK,WAAW;AAChB,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,wBAAwB,eAAe,KAAK,SAAU,QAAO,KAAK;AAE3E,SAAK,sBAAsB;AAC3B,SAAK,WAAW,KAAK,UAAU,EAC5B,KAAK,EACL,iBAAiB,aAAa,KAAK,SAAS,EAC5C,MAAM,CAAC,QAAiB;AACvB,WAAK,WAAW;AAChB,WAAK,sBAAsB;AAC3B,YAAM,IAAI,MAAM,uDAAuD,aAAa,GAAG,CAAC,EAAE;AAAA,IAC5F,CAAC;AACH,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,kBAAkBC,OAA6B;AAC3D,QAAI,CAAC,KAAK,cAAc;AACtB,UAAI,CAAC,KAAK,oBAAoB;AAC5B,cAAM,IAAI,MAAM,2EAA2E;AAAA,MAC7F;AACA,aAAO,KAAK,UAAU,KAAK,oBAAoB,KAAK,qBAAqB;AAAA,IAC3E;AAEA,UAAM,SAAS,MAAM,KAAK,mBAAoB,eAAe,KAAK,aAAa,OAAOA,KAAI;AAC1F,QAAI,OAAO,aAAa,aAAa;AACnC,YAAM,IAAI;AAAA,QACR,6BAA6B,OAAO,QAAQ,2BAA2B,KAAK,aAAa,KAAK;AAAA,MAEhG;AAAA,IACF;AACA,WAAO,KAAK,UAAU,OAAO,YAAY,aAAa,OAAO,YAAY,cAAc;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAAgBA,OAA+B;AAC3D,QAAI,aAAa,KAAKA,KAAI,EAAG,QAAOA;AAEpC,QAAI,CAAC,KAAK,gBAAgB;AACxB,YAAM,IAAI;AAAA,QACR,IAAIA,KAAI;AAAA,MAGV;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,iBAAiB,KAAK;AAC5B,WAAK,YAAY,YAAY;AAC3B,cAAM,EAAE,KAAK,IAAI,MAAM,KAAK,UAAU,EAAE,QAAQ,EAAE,KAAK,cAAc;AACrE,eAAO,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;AAAA,MAC/C,GAAG;AAAA,IACL;AAEA,UAAM,MAAM,MAAM,KAAK;AACvB,UAAM,KAAK,IAAI,IAAIA,KAAI;AACvB,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,MAAM,oBAAoBA,KAAI,4BAA4B,KAAK,cAAc,GAAG;AAAA,IAC5F;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,SAA8C;AAC3D,QAAI;AACJ,QAAI;AACJ,QAAI;AAIF,YAAM,KAAK,kBAAkB,QAAQ,IAAI;AACzC,WAAK,MAAM,KAAK,gBAAgB,QAAQ,IAAI;AAC5C,eAAS,MAAM,KAAK,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;AAAA,IAClD,SAAS,KAAK;AACZ,YAAM,IAAI,MAAM,2BAA2B,QAAQ,IAAI,MAAM,aAAa,GAAG,CAAC,EAAE;AAAA,IAClF;AACA,WAAO,aAAa,OAAO,OAAO,QAAQ,OAAO,EAAE,UAAU,KAAK,MAAM,MAAM,QAAQ,KAAK,CAAC;AAAA,EAC9F;AAAA,EAEA,MAAM,cAAuC;AAC3C,QAAI;AACF,YAAM,KAAK,kBAAkB,0BAA0B;AACvD,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,KAAK;AAAA,IACzC,SAAS,KAAK;AACZ,aAAO,EAAE,UAAU,KAAK,MAAM,IAAI,OAAO,SAAS,aAAa,GAAG,EAAE;AAAA,IACtE;AAAA,EACF;AACF;;;AClNA,IAAM,kBAAkB;AAajB,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YACkB,KACA,QAChB;AACA,UAAM,6BAA6B,GAAG,MAAM,MAAM,EAAE;AAHpC;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAGO,SAAS,YAAY,OAAiC;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,WAAW,QAAQ;AAC/D;AAOO,SAAS,eAAe,KAA+B;AAC5D,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI,iBAAiB,OAAO,GAAG,GAAG,4BAA4B;AAAA,EACtE;AAEA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAQ,WAAW,QAAQ,GAAG;AACjC,UAAM,IAAI,iBAAiB,KAAK,0BAA0B;AAAA,EAC5D;AAEA,QAAM,QAAQ,gBAAgB,KAAK,OAAO;AAC1C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,EAAE,UAAUC,OAAM,KAAK,IAAI;AAClC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,iBAAiB,KAAK,wBAAwB;AAAA,EAC1D;AACA,MAAI,CAACA,OAAM;AACT,UAAM,IAAI,iBAAiB,KAAK,qBAAqB;AAAA,EACvD;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,SAAS,YAAY;AAAA,IAC/B,MAAAA;AAAA,IACA,OAAO,SAAS;AAAA,EAClB;AACF;AAGO,SAAS,kBAAkB,KAAsC;AACtE,MAAI;AACF,WAAO,eAAe,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxCA,SAAS,eAAe,QAAqC;AAC3D,QAAM,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AACnD,QAAM,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AACnD,QAAM,QAAkB,CAAC;AAEzB,aAAW,YAAY,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,YAAY,SAAS,CAAC,CAAC,GAAG;AAC7E,UAAM,QAAQ,KAAK,OAAO,CAAC,OAAO,EAAE,YAAY,eAAe,QAAQ;AACvE,UAAM,KAAK,oCAAoC,QAAQ,IAAI;AAC3D,UAAM,KAAK,KAAK,MAAM,CAAC,EAAG,OAAO,EAAE;AACnC,UAAM,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG;AAC5C,QAAI,OAAQ,OAAM,KAAK,KAAK,MAAM,EAAE;AACpC,UAAM,KAAK,mBAAmB,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EACpE;AAEA,MAAI,KAAK,SAAS,GAAG;AACnB,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,EAAE;AACnC,UAAM,KAAK,qBAAqB,KAAK,MAAM,uBAAuB;AAClE,eAAW,KAAK,KAAM,OAAM,KAAK,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE;AAAA,EAC3E;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YAA4B,QAA6B;AACvD,UAAM,eAAe,MAAM,CAAC;AADF;AAE1B,SAAK,OAAO;AAAA,EACd;AAAA,EAH4B;AAAA;AAAA;AAAA,EAO5B,IAAI,aAAsB;AACxB,WAAO,KAAK,OAAO,SAAS,KAAK,KAAK,OAAO,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM;AAAA,EAC7E;AACF;AAWA,eAAe,WACb,KACA,WACiB;AACjB,QAAM,WAAW,UAAU,IAAI,QAAQ;AACvC,MAAI,CAAC,UAAU;AACb,UAAM,YAAY,OAAO,KAAK,SAAS,EAAE,KAAK,IAAI,KAAK;AACvD,UAAM,IAAI,MAAM,qBAAqB,IAAI,QAAQ,iBAAiB,SAAS,GAAG;AAAA,EAChF;AACA,QAAM,UAA8B,EAAE,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM;AACvE,SAAO,SAAS,SAAS,OAAO;AAClC;AAYA,eAAsB,kBACpB,OACA,SACiC;AACjC,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,SAAiC,CAAC;AACxC,QAAM,UAAmD,CAAC;AAE1D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,OAAW;AACzB,QAAI,CAAC,YAAY,KAAK,GAAG;AACvB,aAAO,GAAG,IAAI;AACd;AAAA,IACF;AACA,QAAI;AACF,cAAQ,KAAK,EAAE,KAAK,KAAK,eAAe,KAAK,EAAE,CAAC;AAAA,IAClD,SAAS,KAAK;AACZ,UAAI,OAAQ,OAAM;AAClB,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,IAAI,CAAC,EAAE,IAAI,MAAM,WAAW,KAAK,QAAQ,SAAS,CAAC;AAAA,EAC7D;AAEA,QAAM,SAA8B,CAAC;AACrC,UAAQ,QAAQ,CAAC,QAAQ,MAAM;AAC7B,UAAM,EAAE,KAAK,IAAI,IAAI,QAAQ,CAAC;AAC9B,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,GAAG,IAAI,OAAO;AAAA,IACvB,OAAO;AACL,YAAM,SAAS,OAAO;AACtB,aAAO,KAAK;AAAA,QACV;AAAA,QACA,KAAK,IAAI;AAAA,QACT,SAAS,aAAa,MAAM;AAAA,QAC5B,MAAM,kBAAkB,mBAAmB,OAAO,OAAO;AAAA,QACzD,UAAU,kBAAkB,mBAAmB,OAAO,WAAW,IAAI;AAAA,QACrE,QAAQ,kBAAkB,mBAAmB,OAAO,SAAS;AAAA,MAC/D,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,uBAAuB,MAAM;AAAA,EACzC;AAEA,SAAO;AACT;AAMA,eAAsB,iBAAiB,SAA2C;AAChF,QAAM,WAAW,MAAM,kBAAkB,QAAQ,KAAK,OAAO;AAC7D,QAAM,cAAwB,CAAC;AAC/B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,QAAI,QAAQ,IAAI,GAAG,MAAM,OAAO;AAC9B,cAAQ,IAAI,GAAG,IAAI;AACnB,kBAAY,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAsB,gBACpB,OACA,SACwB;AACxB,QAAM,UAAyB,CAAC;AAChC,QAAM,gBAAyD,CAAC;AAEhE,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,UAAa,CAAC,YAAY,KAAK,EAAG;AAChD,QAAI;AACF,oBAAc,KAAK,EAAE,KAAK,KAAK,eAAe,KAAK,EAAE,CAAC;AAAA,IACxD,SAAS,KAAK;AACZ,cAAQ,KAAK,EAAE,KAAK,KAAK,OAAO,UAAU,WAAW,IAAI,OAAO,SAAS,aAAa,GAAG,EAAE,CAAC;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,cAAc,IAAI,CAAC,EAAE,IAAI,MAAM,WAAW,KAAK,QAAQ,SAAS,CAAC;AAAA,EACnE;AAEA,UAAQ,QAAQ,CAAC,QAAQ,MAAM;AAC7B,UAAM,EAAE,KAAK,IAAI,IAAI,cAAc,CAAC;AACpC,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,KAAK,IAAI;AAAA,MACT,UAAU,IAAI;AAAA,MACd,IAAI,OAAO,WAAW;AAAA,MACtB,SAAS,OAAO,WAAW,aAAa,aAAa,OAAO,MAAM,IAAI;AAAA,IACxE,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;;;ACjOA,oBAAqC;AAIrC,IAAM,wBACJ;AAgBK,SAAS,wBACd,SACA,QACwB;AACxB,QAAM,SAAS,EAAE,GAAG,OAAO;AAC3B,aAAW,QAAQ,QAAQ,MAAM,OAAO,GAAG;AACzC,UAAM,QAAQ,sBAAsB,KAAK,IAAI;AAC7C,QAAI,CAAC,MAAO;AACZ,UAAM,CAAC,EAAE,KAAK,KAAK,IAAI;AACvB,QAAI,CAAC,OAAO,UAAU,OAAW;AACjC,WAAO,GAAG,IAAI,MAAM,QAAQ;AAAA,EAC9B;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,SAAyC;AACxE,QAAM,aAAS,cAAAC,OAAY,OAAO;AAClC,SAAO,wBAAwB,SAAS,MAAM;AAChD;;;ACxCA,qBAA6B;AAC7B,IAAAC,oBAAiC;AAyB1B,IAAM,kBAAkB;AA+CxB,IAAM,cAAN,cAA0B,MAAM;AAAA,EACrC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAKA,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,sBAAsB,OAAe,QAAuC;AACnF,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,QAAI,eAAe,IAAI,GAAG,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,GAAG,eAAe,eAAe,KAAK,WAAW,GAAG;AAAA,MAGtD;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,WAAW,OAAe,SAAiB,KAAgC;AAClF,QAAM,QAAQ,IAAI,OAAO;AACzB,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,GAAG,eAAe,eAAe,KAAK,+BAA+B,OAAO;AAAA,IAE9E;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,YAAY,KAAa,SAAS,iBAAgC;AAChF,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,IAAI,YAAY,GAAG,MAAM,uBAAwB,IAAc,OAAO,EAAE;AAAA,EAChF;AAKA,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,YAAY,GAAG,MAAM,8BAA8B;AAAA,EAC/D;AAEA,QAAM,YAAa,OAAmC;AACtD,MAAI,OAAO,cAAc,YAAY,cAAc,MAAM;AACvD,UAAM,IAAI,YAAY,GAAG,MAAM,kCAAkC;AAAA,EACnE;AAEA,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,SAAoC,GAAG;AACjF,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,IAAI,YAAY,GAAG,MAAM,eAAe,KAAK,sBAAsB;AAAA,IAC3E;AACA,UAAM,OAAQ,MAA6B;AAC3C,QAAI,SAAS,SAAS,SAAS,eAAe,SAAS,WAAW,SAAS,SAAS;AAClF,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,eAAe,KAAK,cAAc,KAAK,UAAU,IAAI,CAAC;AAAA,MAEjE;AAAA,IACF;AACA,0BAAsB,OAAO,KAAgC;AAAA,EAC/D;AAEA,SAAO;AACT;AASO,SAAS,eACd,QACA,UAA2D,CAAC,GAC1C;AAClB,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,YAAY,QAAQ,aAAa,QAAQ,IAAI;AACnD,QAAM,WAA6B,CAAC;AAEpC,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AAC7D,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,iBAAS,KAAK,IAAI,IAAI,0BAA0B;AAAA,UAC9C,QAAQ,MAAM;AAAA,UACd,YAAY,MAAM;AAAA,UAClB,cAAc,MAAM;AAAA,UACpB,SAAS,MAAM;AAAA,QACjB,CAAC;AACD;AAAA,MACF,KAAK;AACH,iBAAS,KAAK,IAAI,IAAI,kBAAkB;AAAA,UACtC,aAAa,WAAW,OAAO,MAAM,YAAY,oBAAoB,GAAG;AAAA,UACxE,gBAAgB,MAAM,oBAClB,WAAW,OAAO,MAAM,mBAAmB,GAAG,IAC9C,IAAI;AAAA,UACR,QAAQ,MAAM;AAAA,UACd,aAAa,MAAM;AAAA,QACrB,CAAC;AACD;AAAA,MACF,KAAK;AACH,iBAAS,KAAK,IAAI,IAAI,cAAc;AAAA,UAClC,UAAU,MAAM,QAAQ,IAAI;AAAA,UAC5B,OAAO,WAAW,OAAO,MAAM,YAAY,eAAe,GAAG;AAAA,QAC/D,CAAC;AACD;AAAA,MACF,KAAK;AACH,iBAAS,KAAK,IAAI,IAAI,cAAc;AAAA,UAClC,UAAU,MAAM,WAAO,2BAAQ,WAAW,MAAM,IAAI,IAAI;AAAA,QAC1D,CAAC;AACD;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;AASO,SAAS,eACd,MAAc,QAAQ,IAAI,GAC2B;AACrD,MAAI,cAAU,2BAAQ,GAAG;AACzB,aAAS;AACP,UAAM,gBAAY,2BAAQ,SAAS,eAAe;AAClD,QAAI;AACJ,QAAI;AACF,gBAAM,6BAAa,WAAW,MAAM;AAAA,IACtC,QAAQ;AACN,YAAM,aAAS,2BAAQ,OAAO;AAC9B,UAAI,WAAW,QAAS,QAAO;AAC/B,gBAAU;AACV;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,YAAY,KAAK,SAAS,GAAG,MAAM,UAAU;AAAA,EAChE;AACF;;;AZjKO,SAAS,yBAA2C;AACzD,SAAO;AAAA,IACL,KAAK,IAAI,0BAA0B;AAAA,IACnC,OAAO,IAAI,cAAc;AAAA,IACzB,OAAO,IAAI,cAAc;AAAA,IACzB,WAAW,IAAI,kBAAkB;AAAA,EACnC;AACF;AAWO,IAAM,UAAN,MAAc;AAAA,EACV;AAAA,EACA;AAAA,EAET,YAAY,UAA0B,CAAC,GAAG;AACxC,SAAK,YAAY,QAAQ,aAAa,uBAAuB;AAC7D,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA,EAEA,IAAY,gBAA+B;AACzC,WAAO,EAAE,WAAW,KAAK,WAAW,QAAQ,KAAK,OAAO;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAA0B;AAC9B,WAAO,iBAAiB,KAAK,aAAa;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,KAA0E;AACxF,WAAO,kBAAkB,KAAK,KAAK,aAAa;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,aAAa,OAAgC;AACjD,QAAI,CAAC,YAAY,KAAK,EAAG,QAAO;AAChC,UAAM,WAAW,MAAM,kBAAkB,EAAE,WAAW,MAAM,GAAG,KAAK,aAAa;AACjF,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MACJ,MAA0C,QAAQ,KACI;AACtD,WAAO,gBAAgB,KAAK,KAAK,aAAa;AAAA,EAChD;AACF;AAGO,IAAM,UAAU,IAAI,QAAQ;","names":["path","path","path","vaultFactory","path","path","path","path","parseDotenv","import_node_path"]}
|