@super-repo/envx-plugins 0.2.3-b.5 → 0.2.3-b.6

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.
Files changed (2) hide show
  1. package/README.md +55 -339
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,380 +1,96 @@
1
1
  # @super-repo/envx-plugins
2
2
 
3
- Secret-provider plugins for [envx](https://www.npmjs.com/package/@super-repo/envx). Each plugin returns a `SecretProvider` you wire into envx's `resolvers:` config so values like `${aws-secrets:my-db}` and `${vault:prod/api}` resolve at load time from the matching backend.
3
+ > Secret-provider plugins for envx `${aws-secrets:my-db}` and `${vault:prod/api}` style refs that resolve at load time from real backends.
4
4
 
5
- ```sh
6
- pnpm add @super-repo/envx-plugins
7
- ```
8
-
9
- The plugins package itself has zero runtime SDK dependencies. Each plugin lazy-loads its provider's SDK on first use; install only the SDKs you actually need:
5
+ [![NPM](https://nodei.co/npm/@super-repo/envx-plugins.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/@super-repo/envx-plugins)
10
6
 
11
- | provider | install |
12
- | ------------------ | --------------------------------------------------------------------------- |
13
- | AWS Secrets Manager | `pnpm add @aws-sdk/client-secrets-manager` |
14
- | GCP Secret Manager | `pnpm add @google-cloud/secret-manager` |
15
- | Azure Key Vault | `pnpm add @azure/keyvault-secrets @azure/identity` |
16
- | HashiCorp Vault | _(no SDK — uses native `fetch`)_ |
17
- | 1Password (SDK) | `pnpm add @1password/sdk` |
18
- | 1Password (CLI) | install `op` via [1Password's docs](https://developer.1password.com/docs/cli) |
19
- | Doppler | _(no SDK — uses native `fetch`)_ |
20
- | Infisical | _(no SDK — uses native `fetch`)_ |
7
+ [![npm version](https://img.shields.io/npm/v/@super-repo/envx-plugins.svg)](https://www.npmjs.com/package/@super-repo/envx-plugins)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@super-repo/envx-plugins.svg)](https://www.npmjs.com/package/@super-repo/envx-plugins)
9
+ [![license](https://img.shields.io/npm/l/@super-repo/envx-plugins.svg)](./LICENSE)
21
10
 
22
- ## How it composes with envx
11
+ `.env*` files are great until the secrets in them shouldn't sit on disk. envx-plugins lets you write `${aws-secrets:prod/db}` (or vault, 1Password, Doppler, …) directly in `.env*` and have envx resolve those refs against the real backend at load time. Each plugin returns a `SecretProvider` you wire into [envx's](https://www.npmjs.com/package/@super-repo/envx) `resolvers:` config.
23
12
 
24
- envx's `resolvers:` config takes a **synchronous** function (`(id) => string | undefined`). All real secret-store SDKs are async, so plugins split the concern in two:
13
+ The package itself has zero runtime SDK dependencies. Each plugin lazy-loads its provider's SDK on first use, so you only install the SDKs you actually reference.
25
14
 
26
- 1. **`preload(ids)`** — async batch fetch, populates an in-memory cache.
27
- 2. **`resolve(id)`** — sync cache read. This is what envx calls during load.
15
+ ## Getting started
28
16
 
29
- You call `preload` in your bootstrap (top-level `await` in an async entry, or inside a small `await main()` wrapper), then hand `resolve` to envx. The cache is per-provider-instance — one `awsSecrets({...})` call yields one shared cache.
30
-
31
- ## One-shot example — all 7 providers
17
+ ```sh
18
+ pnpm add @super-repo/envx-plugins
19
+ ```
32
20
 
33
21
  ```ts
34
22
  // bootstrap.ts
35
23
  import envx from "@super-repo/envx";
36
- import {
37
- awsSecrets,
38
- gcpSecrets,
39
- azureKeyVault,
40
- hcVault,
41
- onePassword,
42
- doppler,
43
- infisical,
44
- autoPreload,
45
- asResolvers,
46
- } from "@super-repo/envx-plugins";
24
+ import { awsSecrets, autoPreload, asResolvers } from "@super-repo/envx-plugins";
47
25
 
48
26
  const aws = awsSecrets({ region: "us-east-1" });
49
- const gcp = gcpSecrets({ projectId: "acme-prod" });
50
- const azure = azureKeyVault({ vaultUrl: "https://acme.vault.azure.net" });
51
- const vault = hcVault({
52
- endpoint: process.env.VAULT_ADDR!,
53
- token: process.env.VAULT_TOKEN!,
54
- });
55
- const op = onePassword({ token: process.env.OP_SERVICE_ACCOUNT_TOKEN });
56
- const dop = doppler({ token: process.env.DOPPLER_TOKEN! });
57
- const inf = infisical({
58
- token: process.env.INFISICAL_TOKEN!,
59
- projectId: "proj-1",
60
- environment: "prod",
61
- });
62
27
 
63
- // One call, per-file scan, parallel resolution.
64
- await autoPreload([aws, gcp, azure, vault, op, dop, inf], {
65
- envFiles: [".env", "vault/.env.prod"],
66
- });
28
+ await autoPreload([aws], { envFiles: [".env"] });
67
29
 
68
30
  envx({
69
- // `asResolvers([...])` builds the { name → resolve } map for you.
70
- resolvers: asResolvers([aws, gcp, azure, vault, op, dop, inf]),
31
+ resolvers: asResolvers([aws]),
71
32
  });
72
33
  ```
73
34
 
74
- In your `.env*` files:
75
-
76
- ```
77
- DATABASE_URL=${aws-secrets:prod/db}
78
- CACHE_URL=${gcp-secrets:cache-host}
79
- JWT_SECRET=${azure-keyvault:prod-jwt-signing@v3}
80
- ROOT_TOKEN=${vault:prod/admin#token}
81
- GITHUB_PAT=${op:op://prod/GitHub/personal-access-token}
82
- SENDGRID_KEY=${doppler:SENDGRID_KEY}
83
- STRIPE_KEY=${infisical:STRIPE_LIVE_KEY}
84
- ```
85
-
86
- Only the providers actually referenced in those files get pre-loaded — `autoPreload` scans the files first and skips providers with zero hits.
87
-
88
- ## Per-provider usage
89
-
90
- ### AWS Secrets Manager
91
-
92
- ```ts
93
- import { awsSecrets } from "@super-repo/envx-plugins/aws";
94
-
95
- const aws = awsSecrets({ region: "us-east-1" });
96
- await aws.preload(["prod/db", "prod/api-key"]);
97
- ```
98
-
99
35
  ```
36
+ # .env
100
37
  DATABASE_URL=${aws-secrets:prod/db}
101
38
  API_KEY=${aws-secrets:prod/api-key}
102
39
  ```
103
40
 
104
- Multiple regions? Give each its own provider name and reference accordingly:
105
-
106
- ```ts
107
- const awsUs = awsSecrets({ region: "us-east-1", name: "aws-us" });
108
- const awsEu = awsSecrets({ region: "eu-west-1", name: "aws-eu" });
109
- ```
110
-
111
- ```
112
- DB_US=${aws-us:prod/db}
113
- DB_EU=${aws-eu:prod/db}
114
- ```
115
-
116
- ### GCP Secret Manager
117
-
118
- ```ts
119
- import { gcpSecrets } from "@super-repo/envx-plugins/gcp";
120
-
121
- const gcp = gcpSecrets({ projectId: "acme-prod" });
122
- await gcp.preload(["prod-db", "prod-api"]);
123
- ```
124
-
125
- ```
126
- DB_URL=${gcp-secrets:prod-db}
127
- API_KEY=${gcp-secrets:prod-api@3} # pin to version 3
128
- ```
129
-
130
- ### Azure Key Vault
131
-
132
- ```ts
133
- import { azureKeyVault } from "@super-repo/envx-plugins/azure";
134
-
135
- const az = azureKeyVault({
136
- vaultUrl: "https://my-vault.vault.azure.net",
137
- });
138
- await az.preload(["prod-db", "prod-jwt-signing"]);
139
- ```
140
-
141
- ```
142
- DB_URL=${azure-keyvault:prod-db}
143
- JWT_SECRET=${azure-keyvault:prod-jwt-signing@<version>}
144
- ```
145
-
146
- Credentials come from `DefaultAzureCredential` by default (env vars → managed identity → Azure CLI → VS Code, in that order). Pass `credential:` to override.
147
-
148
- ### HashiCorp Vault (KV v1 / v2)
149
-
150
- ```ts
151
- import { hcVault } from "@super-repo/envx-plugins/vault";
152
-
153
- const vault = hcVault({
154
- endpoint: process.env.VAULT_ADDR!,
155
- token: process.env.VAULT_TOKEN!,
156
- mount: "secret",
157
- });
158
- await vault.preload(["prod/db", "prod/api"]);
159
- ```
160
-
161
- ```
162
- DB_URL=${vault:prod/db} # reads `data.value` field by default
163
- USERNAME=${vault:prod/db#username} # specific JSON field
164
- ```
165
-
166
- KV v1? Pass `kvVersion: 1`.
167
-
168
- ### 1Password — CLI mode (developer machines)
169
-
170
- If `op` is signed in on your machine, no token / SDK is required:
171
-
172
- ```ts
173
- import { onePassword } from "@super-repo/envx-plugins/op";
174
-
175
- const op = onePassword();
176
- await op.preload([
177
- "op://prod/Database/url",
178
- "op://prod/API/key",
179
- ]);
180
- ```
181
-
182
- ```
183
- DB_URL=${op:op://prod/Database/url}
184
- API_KEY=${op:op://prod/API/key}
185
- ```
186
-
187
- ### 1Password — SDK mode (CI / containers)
188
-
189
- Use a service-account token in environments without an interactive `op signin`:
190
-
191
- ```ts
192
- const op = onePassword({
193
- token: process.env.OP_SERVICE_ACCOUNT_TOKEN!,
194
- });
195
- ```
196
-
197
- The plugin lazy-loads `@1password/sdk` only in this mode.
198
-
199
- ### Doppler
200
-
201
- ```ts
202
- import { doppler } from "@super-repo/envx-plugins/doppler";
203
-
204
- const dop = doppler({ token: process.env.DOPPLER_TOKEN! });
205
- await dop.preload(["DATABASE_URL", "API_KEY"]);
206
- ```
207
-
208
- ```
209
- DATABASE_URL=${doppler:DATABASE_URL}
210
- API_KEY=${doppler:API_KEY}
211
- ```
212
-
213
- Service tokens scope to one project + config, so you typically don't pass project/config explicitly.
41
+ For the all-seven-providers wiring, per-provider auth, edge-runtime usage, and custom backends, see [Docs](#docs).
214
42
 
215
- ### Infisical
216
-
217
- ```ts
218
- import { infisical } from "@super-repo/envx-plugins/infisical";
219
-
220
- const inf = infisical({
221
- token: process.env.INFISICAL_TOKEN!,
222
- projectId: "proj-1",
223
- environment: "prod",
224
- secretPath: "/services/api", // optional
225
- });
226
- await inf.preload(["DATABASE_URL", "API_KEY"]);
227
- ```
43
+ ## Features
228
44
 
229
- ```
230
- DATABASE_URL=${infisical:DATABASE_URL}
231
- API_KEY=${infisical:API_KEY}
232
- ```
45
+ | provider | edge-runtime | reference syntax |
46
+ | ---------------------- | :----------: | ------------------------------------------------- |
47
+ | **AWS Secrets Manager** | ⚠ | `${aws-secrets:<name>}` (`@v3` for version pin) |
48
+ | **GCP Secret Manager** | ⚠ | `${gcp-secrets:<name>}` (`@3` for version pin) |
49
+ | **Azure Key Vault** | ⚠ | `${azure-keyvault:<name>}` (`@<version>` for pin) |
50
+ | **HashiCorp Vault** | ✓ | `${vault:<path>}` (`#<field>` for JSON field) |
51
+ | **1Password (CLI)** | ✗ | `${op:op://vault/item/field}` |
52
+ | **1Password (SDK)** | depends | `${op:op://vault/item/field}` |
53
+ | **Doppler** | ✓ | `${doppler:<NAME>}` |
54
+ | **Infisical** | ✓ | `${infisical:<NAME>}` |
233
55
 
234
- Self-hosted? Pass `endpoint: "https://infisical.example.com"`.
56
+ ✓ = native `fetch`, runs in Workers / Edge / Deno. ⚠ = needs Node-compatible runtime (Lambda, Cloud Run, Node). ✗ = spawns a child process. Full table with caveats: [docs/runtime.md](./docs/runtime.md#edge-runtime-compatibility-per-provider).
235
57
 
236
- ## Runtime / edge — secrets that never leave the source
58
+ Beyond the per-provider plugins:
237
59
 
238
- For the "secrets never touch disk, fetched on demand" model, use `createSecretRuntime` from the `/runtime` subpath. Async-first, TTL-cached, V8-isolate-safe works in Cloudflare Workers, Vercel Edge, Deno Deploy, AWS Lambda, Bun, Node.
60
+ - **`autoPreload`** scans your `envFiles` first, only invokes providers that actually appear, and runs them in parallel.
61
+ - **`asResolvers`** — turns a provider list into the `{ name → resolve }` map envx expects.
62
+ - **`buildProvider`** — wrap any async `(id) => Promise<string>` fetcher into a first-class `SecretProvider` for in-house backends.
63
+ - **`createSecretRuntime`** (`/runtime` subpath) — V8-isolate-safe, TTL-cached, single-flight async fetch for edge / serverless.
239
64
 
240
- ```ts
241
- // src/secrets.ts — module-level singleton, one instance per worker / process
242
- import { createSecretRuntime } from "@super-repo/envx-plugins/runtime";
243
- import { hcVault } from "@super-repo/envx-plugins/vault";
65
+ ## Comparison
244
66
 
245
- export const secrets = createSecretRuntime({
246
- providers: [
247
- hcVault({
248
- endpoint: env.VAULT_ADDR,
249
- token: env.VAULT_TOKEN,
250
- }),
251
- ],
252
- ttl: 300, // refresh after 5 min
253
- onFailure: "cache-stale", // serve stale on transient backend failures
254
- });
255
- ```
67
+ | feature | `@super-repo/envx-plugins` | `dotenv-vault` | direct vendor SDKs |
68
+ | -------------------------------------- | :------------------------: | :----------------: | :----------------: |
69
+ | `${ref}` interpolation in `.env*` | ✓ | ✗ | ✗ |
70
+ | Pluggable, multi-vendor in one config | ✓ | ✗ (one backend) | n/a |
71
+ | Lazy SDK install per provider | ✓ | n/a | ✗ |
72
+ | Edge-runtime entry (V8-isolate-safe) | ✓ | ✗ | varies |
73
+ | Works without leaving envx's pipeline | ✓ | replaces dotenv | replaces dotenv |
256
74
 
257
- ```ts
258
- // Cloudflare Worker, Vercel Edge, Lambda, Deno Deploy — same handler shape
259
- import { secrets } from "./secrets";
75
+ `dotenv-vault` and the official Doppler / Infisical CLIs each take over the entire env story. envx-plugins keeps envx as the loader and adds vendor-backed `${ref}` resolution as a layer on top — so one repo can mix AWS for DB credentials, Vault for service tokens, and 1Password for developer secrets without three different bootstrap shapes.
260
76
 
261
- export default async function handler(req: Request): Promise<Response> {
262
- const dbUrl = await secrets.get("vault:prod/db");
263
- const apiKey = await secrets.get("vault:prod/api");
264
- return new Response(JSON.stringify({ dbUrl, apiKey }));
265
- }
266
- ```
77
+ ## Docs
267
78
 
268
- ### Behavior
79
+ Deep references and worked examples live alongside this package in [./docs/](./docs/):
269
80
 
270
- - **Lazy fetch** — first read triggers the network call. Subsequent reads inside the TTL window hit the in-memory cache.
271
- - **Single-flight** — N concurrent reads of the same ref coalesce to one fetch. Edge functions handling a request burst don't fan out N parallel calls to your secret store.
272
- - **TTL refresh** — values older than `ttl` seconds are refetched on next read.
273
- - **Failure modes** — `"throw"` (default), `"cache-stale"` (serve previous value, log warn), `"cache-error"` (cache the failure for `errorTtl` seconds so a flaky backend doesn't get hammered).
274
- - **No fs / child_process imports** the runtime entry is V8-isolate-safe.
81
+ - **[providers.md](./docs/providers.md)** — per-provider auth, options, and `${ref}` syntax for AWS, GCP, Azure, Vault, 1Password (CLI + SDK), Doppler, Infisical.
82
+ - **[recipes.md](./docs/recipes.md)** — the all-seven-providers bootstrap, plus the SDK install matrix.
83
+ - **[runtime.md](./docs/runtime.md)** — `createSecretRuntime` for edge / serverless: TTL caching, single-flight, failure modes, edge-compat table.
84
+ - **[custom-providers.md](./docs/custom-providers.md)** wrap any backend with `buildProvider`.
85
+ - **[library-api.md](./docs/library-api.md)** `SecretProvider` shape, `autoPreload`, `asResolvers`, `buildProvider`, `MissingSdkError`, subpath imports.
275
86
 
276
- ### API
87
+ ## References
277
88
 
278
- ```ts
279
- interface SecretRuntime {
280
- get(ref: string): Promise<string>; // throws on miss / unknown provider / failed fetch
281
- tryGet(ref: string): Promise<string | undefined>; // never throws
282
- invalidate(ref: string): Promise<string>; // force-refresh a specific ref
283
- clear(): void; // drop all cached entries
284
- readonly stats: () => { cached: number; inflight: number; errors: number };
285
- }
286
-
287
- interface SecretRuntimeOptions {
288
- readonly providers: readonly SecretProvider[];
289
- readonly ttl?: number; // default 300 (5min). 0 = no cache. Infinity = forever.
290
- readonly onFailure?: "throw" | "cache-stale" | "cache-error";
291
- readonly errorTtl?: number; // for "cache-error" mode. default 30
292
- readonly logger?: { warn(msg: string): void };
293
- }
294
- ```
295
-
296
- ### Edge-runtime compatibility per provider
297
-
298
- | provider | Cloudflare Workers / Vercel Edge / Deno | Lambda / Cloud Run / Node | reason |
299
- | --------------------- | --------------------------------------- | ------------------------- | ---------------------------- |
300
- | `hcVault` | ✓ | ✓ | native `fetch` |
301
- | `doppler` | ✓ | ✓ | native `fetch` |
302
- | `infisical` | ✓ | ✓ | native `fetch` |
303
- | `awsSecrets` | ⚠ | ✓ | Node streams in the SDK |
304
- | `gcpSecrets` | ⚠ | ✓ | gRPC in the SDK |
305
- | `azureKeyVault` | ⚠ | ✓ | `@azure/identity` token chain |
306
- | `onePassword` (CLI) | ✗ | ✓ (with `op` on PATH) | spawns a child process |
307
- | `onePassword` (SDK) | depends on `@1password/sdk` | ✓ | SDK's own constraints |
308
- | custom `buildProvider`| depends on what your fetcher does | ✓ | usually fine if you use `fetch` |
309
-
310
- For maximum portability across edges, prefer Vault / Doppler / Infisical, or write your own provider with `buildProvider`:
311
-
312
- ```ts
313
- import { buildProvider } from "@super-repo/envx-plugins";
314
-
315
- const myProvider = buildProvider("internal", async (id) => {
316
- const r = await fetch(`https://internal.example.com/secrets/${id}`, {
317
- headers: { Authorization: `Bearer ${env.MY_TOKEN}` },
318
- });
319
- return (await r.json()).value;
320
- });
321
- ```
322
-
323
- That single-file plugin works in every JS runtime that has `fetch`.
324
-
325
- ## Custom providers
326
-
327
- The same `SecretProvider` shape works for any backend — wrap it with `buildProvider`:
328
-
329
- ```ts
330
- import { buildProvider, type SecretProvider } from "@super-repo/envx-plugins";
331
-
332
- const myProvider: SecretProvider = buildProvider("my-backend", async (id) => {
333
- const r = await fetch(`https://internal.example.com/secrets/${id}`, {
334
- headers: { Authorization: `Bearer ${process.env.MY_TOKEN}` },
335
- });
336
- if (!r.ok) throw new Error(`fetch ${id}: ${r.status}`);
337
- const body = await r.json() as { value: string };
338
- return body.value;
339
- });
340
-
341
- await myProvider.preload(["a", "b"]);
342
- envx({ resolvers: { [myProvider.name]: myProvider.resolve } });
343
- ```
344
-
345
- ```
346
- SOMETHING=${my-backend:a}
347
- ELSE=${my-backend:b}
348
- ```
349
-
350
- ## API
351
-
352
- ```ts
353
- interface SecretProvider {
354
- readonly name: string;
355
- preload(ids: readonly string[]): Promise<void>; // batch fetch + cache
356
- fetch(id: string): Promise<string>; // single fetch, bypasses cache (for runtime use)
357
- resolve(id: string): string | undefined; // sync cache read (for envx resolvers)
358
- readonly cache: ReadonlyMap<string, string>;
359
- }
360
-
361
- function autoPreload(
362
- providers: readonly SecretProvider[],
363
- opts: { envFiles: string[]; cwd?: string },
364
- ): Promise<{ preloaded: Record<string, string[]> }>;
365
-
366
- function asResolvers(
367
- providers: readonly SecretProvider[],
368
- ): Record<string, (id: string) => string | undefined>;
369
-
370
- function buildProvider(
371
- name: string,
372
- fetchOne: (id: string) => Promise<string>,
373
- ): SecretProvider;
374
-
375
- class MissingSdkError extends Error { /* … */ }
376
- ```
89
+ - [`@super-repo/envx`](../core) — the loader these plugins plug into. envx-plugins has no purpose without it; the two are designed to be used together.
90
+ - [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/) · [GCP Secret Manager](https://cloud.google.com/secret-manager/docs) · [Azure Key Vault](https://learn.microsoft.com/azure/key-vault/) · [HashiCorp Vault KV](https://developer.hashicorp.com/vault/docs/secrets/kv)
91
+ - [1Password Service Accounts](https://developer.1password.com/docs/service-accounts) · [Doppler API](https://docs.doppler.com/reference) · [Infisical API](https://infisical.com/docs/api-reference/overview/introduction)
92
+ - [Project README](../../../README.md)
377
93
 
378
94
  ## License
379
95
 
380
- MIT
96
+ MIT © Super-Repo contributors. See [LICENSE](./LICENSE).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@super-repo/envx-plugins",
3
3
  "description": "Collections of plugins for envx — AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, 1Password, Doppler, Infisical. Each plugin returns a SecretProvider compatible with envx's `resolvers:` config.",
4
- "version": "0.2.3-b.5",
4
+ "version": "0.2.3-b.6",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -65,15 +65,15 @@
65
65
  "config": "../../../config/rune-envx.config.ts"
66
66
  },
67
67
  "peerDependencies": {
68
- "@super-repo/envx": "0.2.3-b.5"
68
+ "@super-repo/envx": "0.2.3-b.6"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^22.10.0",
72
72
  "tsc-alias": "^1.8.16",
73
73
  "typescript": "^6.0.3",
74
74
  "vitest": "^4.1.5",
75
- "@super-repo/envx": "0.2.3-b.5",
76
- "@super-repo/cli": "0.2.3-b.5",
75
+ "@super-repo/cli": "0.2.3-b.6",
76
+ "@super-repo/envx": "0.2.3-b.6",
77
77
  "@super-repo/envx-libs": "0.0.1"
78
78
  }
79
79
  }