@envshed/node 0.1.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 +1055 -0
- package/dist/index.cjs +423 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +375 -0
- package/dist/index.d.ts +375 -0
- package/dist/index.js +394 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,1055 @@
|
|
|
1
|
+
# @envshed/node
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [Envshed](https://envshed.com) — secrets management for teams.
|
|
4
|
+
|
|
5
|
+
Read, write, and manage your Envshed secrets programmatically from any JavaScript or TypeScript project.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Full coverage of the Envshed REST API
|
|
10
|
+
- Zero runtime dependencies — uses native `fetch` (Node 18+)
|
|
11
|
+
- First-class TypeScript support with complete type definitions
|
|
12
|
+
- Automatic retries with exponential backoff for transient failures
|
|
13
|
+
- Namespaced API methods for clean, discoverable usage
|
|
14
|
+
- Dual ESM and CommonJS output
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# npm
|
|
20
|
+
npm install @envshed/node
|
|
21
|
+
|
|
22
|
+
# pnpm
|
|
23
|
+
pnpm add @envshed/node
|
|
24
|
+
|
|
25
|
+
# yarn
|
|
26
|
+
yarn add @envshed/node
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Requirements:** Node.js 18 or later.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { EnvshedClient } from "@envshed/node";
|
|
35
|
+
|
|
36
|
+
const client = new EnvshedClient({
|
|
37
|
+
token: process.env.ENVSHED_TOKEN!,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Fetch all secrets for an environment
|
|
41
|
+
const { secrets } = await client.secrets.get({
|
|
42
|
+
org: "my-org",
|
|
43
|
+
project: "my-project",
|
|
44
|
+
env: "production",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(secrets.DATABASE_URL);
|
|
48
|
+
console.log(secrets.API_KEY);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Authentication
|
|
52
|
+
|
|
53
|
+
The SDK supports two types of tokens:
|
|
54
|
+
|
|
55
|
+
- **User API tokens** (`envshed_...`) — created from the Envshed dashboard under Settings > API Tokens. These tokens inherit the permissions of the user who created them.
|
|
56
|
+
- **Service tokens** (`envshed_svc_...`) — created for CI/CD and machine-to-machine access. These can be scoped to an organization, project, or environment with `read_only` or `read_write` permissions.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Using a user API token
|
|
60
|
+
const client = new EnvshedClient({
|
|
61
|
+
token: "envshed_abc123...",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Using a service token
|
|
65
|
+
const client = new EnvshedClient({
|
|
66
|
+
token: "envshed_svc_xyz789...",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Client Configuration
|
|
71
|
+
|
|
72
|
+
### `EnvshedClientOptions`
|
|
73
|
+
|
|
74
|
+
| Option | Type | Default | Description |
|
|
75
|
+
|--------|------|---------|-------------|
|
|
76
|
+
| `token` | `string` | *(required)* | API authentication token |
|
|
77
|
+
| `apiUrl` | `string` | `"https://app.envshed.com"` | Base URL for the Envshed API |
|
|
78
|
+
| `retry` | `RetryOptions` | See below | Retry configuration for transient failures |
|
|
79
|
+
|
|
80
|
+
### `RetryOptions`
|
|
81
|
+
|
|
82
|
+
| Option | Type | Default | Description |
|
|
83
|
+
|--------|------|---------|-------------|
|
|
84
|
+
| `maxRetries` | `number` | `3` | Maximum retry attempts. Set to `0` to disable retries. |
|
|
85
|
+
| `initialDelayMs` | `number` | `1000` | Initial delay between retries in milliseconds |
|
|
86
|
+
| `maxDelayMs` | `number` | `10000` | Maximum delay cap in milliseconds |
|
|
87
|
+
| `backoff` | `"exponential" \| "linear" \| BackoffFunction` | `"exponential"` | Backoff strategy between retries (see [Retry Strategy](#retry-strategy)) |
|
|
88
|
+
| `shouldRetry` | `ShouldRetryFunction` | 5xx + network errors | Custom function to decide which errors to retry |
|
|
89
|
+
| `onRetry` | `OnRetryFunction` | `undefined` | Callback invoked before each retry attempt |
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const client = new EnvshedClient({
|
|
93
|
+
token: "envshed_...",
|
|
94
|
+
apiUrl: "https://custom-instance.example.com",
|
|
95
|
+
retry: {
|
|
96
|
+
maxRetries: 5,
|
|
97
|
+
initialDelayMs: 500,
|
|
98
|
+
maxDelayMs: 15000,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
By default, retries use exponential backoff with jitter. Only 5xx server errors and network failures are retried — 4xx client errors are never retried. See the [Retry Strategy](#retry-strategy) section for full customization.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## API Reference
|
|
108
|
+
|
|
109
|
+
All methods are `async` and return typed promises. The client organizes methods into namespaced sub-APIs.
|
|
110
|
+
|
|
111
|
+
### `client.me()`
|
|
112
|
+
|
|
113
|
+
Check the authenticated identity.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const me = await client.me();
|
|
117
|
+
|
|
118
|
+
if ("email" in me) {
|
|
119
|
+
// User token
|
|
120
|
+
console.log(me.email); // "user@example.com"
|
|
121
|
+
} else {
|
|
122
|
+
// Service token
|
|
123
|
+
console.log(me.type); // "service_token"
|
|
124
|
+
console.log(me.org); // "my-org" | null
|
|
125
|
+
console.log(me.scope); // "org" | "project" | "environment"
|
|
126
|
+
console.log(me.permission); // "read" | "read_write"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Returns:** `MeResponse` — either `{ email: string }` for user tokens or `{ type: "service_token", org, scope, permission }` for service tokens.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### Secrets
|
|
135
|
+
|
|
136
|
+
The most commonly used API. Read and write environment variables.
|
|
137
|
+
|
|
138
|
+
#### `client.secrets.get(path)`
|
|
139
|
+
|
|
140
|
+
Retrieve all secrets for an environment. Values are returned decrypted.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const result = await client.secrets.get({
|
|
144
|
+
org: "my-org",
|
|
145
|
+
project: "my-project",
|
|
146
|
+
env: "production",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log(result.secrets); // { DATABASE_URL: "postgres://...", API_KEY: "sk_..." }
|
|
150
|
+
console.log(result.version); // 42
|
|
151
|
+
console.log(result.placeholders); // ["PLACEHOLDER_KEY"]
|
|
152
|
+
console.log(result.linkedKeys); // ["SHARED_SECRET"]
|
|
153
|
+
console.log(result.decryptErrors); // [] (keys that failed to decrypt)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Parameters:**
|
|
157
|
+
|
|
158
|
+
| Param | Type | Description |
|
|
159
|
+
|-------|------|-------------|
|
|
160
|
+
| `path.org` | `string` | Organization slug |
|
|
161
|
+
| `path.project` | `string` | Project slug |
|
|
162
|
+
| `path.env` | `string` | Environment slug |
|
|
163
|
+
|
|
164
|
+
**Returns:** `GetSecretsResponse`
|
|
165
|
+
|
|
166
|
+
| Field | Type | Description |
|
|
167
|
+
|-------|------|-------------|
|
|
168
|
+
| `secrets` | `Record<string, string>` | Key-value pairs of decrypted secrets |
|
|
169
|
+
| `placeholders` | `string[]` | Keys that are placeholders (no real value) |
|
|
170
|
+
| `version` | `number` | Current environment version number |
|
|
171
|
+
| `linkedKeys` | `string[]` | Keys shared from linked projects (optional) |
|
|
172
|
+
| `decryptErrors` | `string[]` | Keys that failed to decrypt (optional) |
|
|
173
|
+
|
|
174
|
+
#### `client.secrets.set(path, secrets)`
|
|
175
|
+
|
|
176
|
+
Create or update secrets in an environment. Existing keys are updated; new keys are created.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const result = await client.secrets.set(
|
|
180
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
181
|
+
{
|
|
182
|
+
DATABASE_URL: "postgres://prod-host/mydb",
|
|
183
|
+
API_KEY: "sk_live_new_key",
|
|
184
|
+
NEW_SECRET: "new_value",
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
console.log(result.ok); // true
|
|
189
|
+
console.log(result.version); // 43
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Parameters:**
|
|
193
|
+
|
|
194
|
+
| Param | Type | Description |
|
|
195
|
+
|-------|------|-------------|
|
|
196
|
+
| `path` | `EnvPath` | Organization, project, and environment slugs |
|
|
197
|
+
| `secrets` | `Record<string, string>` | Key-value pairs to upsert |
|
|
198
|
+
|
|
199
|
+
**Returns:** `SetSecretsResponse` — `{ ok: true, version: number }`
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Organizations
|
|
204
|
+
|
|
205
|
+
#### `client.orgs.list()`
|
|
206
|
+
|
|
207
|
+
List all organizations the authenticated user belongs to.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const { organizations } = await client.orgs.list();
|
|
211
|
+
|
|
212
|
+
for (const org of organizations) {
|
|
213
|
+
console.log(`${org.name} (${org.slug}) — role: ${org.role}`);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Returns:** `ListOrgsResponse` — `{ organizations: Organization[] }`
|
|
218
|
+
|
|
219
|
+
Each `Organization` has:
|
|
220
|
+
|
|
221
|
+
| Field | Type | Description |
|
|
222
|
+
|-------|------|-------------|
|
|
223
|
+
| `name` | `string` | Organization display name |
|
|
224
|
+
| `slug` | `string` | URL-safe identifier |
|
|
225
|
+
| `role` | `string` | User's role: `"owner"`, `"admin"`, or `"member"` |
|
|
226
|
+
|
|
227
|
+
#### `client.orgs.create(data)`
|
|
228
|
+
|
|
229
|
+
Create a new organization.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const { organization } = await client.orgs.create({
|
|
233
|
+
name: "Acme Corp",
|
|
234
|
+
slug: "acme-corp", // optional — auto-generated from name if omitted
|
|
235
|
+
description: "Main org", // optional
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(organization.slug); // "acme-corp"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Parameters:**
|
|
242
|
+
|
|
243
|
+
| Param | Type | Required | Description |
|
|
244
|
+
|-------|------|----------|-------------|
|
|
245
|
+
| `name` | `string` | Yes | Organization display name |
|
|
246
|
+
| `slug` | `string` | No | URL-safe identifier (auto-generated if omitted) |
|
|
247
|
+
| `description` | `string` | No | Description |
|
|
248
|
+
|
|
249
|
+
**Returns:** `CreateOrgResponse` — `{ organization: { name, slug } }`
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### Projects
|
|
254
|
+
|
|
255
|
+
#### `client.projects.list(org)`
|
|
256
|
+
|
|
257
|
+
List all projects within an organization.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const { projects } = await client.projects.list("my-org");
|
|
261
|
+
|
|
262
|
+
for (const project of projects) {
|
|
263
|
+
console.log(`${project.name} (${project.slug})`);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
|
|
269
|
+
| Param | Type | Description |
|
|
270
|
+
|-------|------|-------------|
|
|
271
|
+
| `org` | `string` | Organization slug |
|
|
272
|
+
|
|
273
|
+
**Returns:** `ListProjectsResponse` — `{ projects: Project[] }`
|
|
274
|
+
|
|
275
|
+
Each `Project` has:
|
|
276
|
+
|
|
277
|
+
| Field | Type | Description |
|
|
278
|
+
|-------|------|-------------|
|
|
279
|
+
| `id` | `string` | Unique identifier |
|
|
280
|
+
| `name` | `string` | Project display name |
|
|
281
|
+
| `slug` | `string` | URL-safe identifier |
|
|
282
|
+
| `description` | `string \| null` | Description |
|
|
283
|
+
|
|
284
|
+
#### `client.projects.create(org, data)`
|
|
285
|
+
|
|
286
|
+
Create a new project. Automatically creates three default environments: development, staging, and production.
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const { project } = await client.projects.create("my-org", {
|
|
290
|
+
name: "Backend API",
|
|
291
|
+
description: "Main backend service", // optional
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
console.log(project.slug); // "backend-api"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Parameters:**
|
|
298
|
+
|
|
299
|
+
| Param | Type | Required | Description |
|
|
300
|
+
|-------|------|----------|-------------|
|
|
301
|
+
| `org` | `string` | Yes | Organization slug |
|
|
302
|
+
| `name` | `string` | Yes | Project display name |
|
|
303
|
+
| `description` | `string` | No | Description |
|
|
304
|
+
|
|
305
|
+
**Returns:** `CreateProjectResponse` — `{ project: { name, slug } }`
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### Environments
|
|
310
|
+
|
|
311
|
+
#### `client.environments.list(org, project)`
|
|
312
|
+
|
|
313
|
+
List all environments within a project.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const { environments } = await client.environments.list("my-org", "my-project");
|
|
317
|
+
|
|
318
|
+
for (const env of environments) {
|
|
319
|
+
console.log(`${env.name} (${env.slug})`);
|
|
320
|
+
}
|
|
321
|
+
// "Development (development)"
|
|
322
|
+
// "Staging (staging)"
|
|
323
|
+
// "Production (production)"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Parameters:**
|
|
327
|
+
|
|
328
|
+
| Param | Type | Description |
|
|
329
|
+
|-------|------|-------------|
|
|
330
|
+
| `org` | `string` | Organization slug |
|
|
331
|
+
| `project` | `string` | Project slug |
|
|
332
|
+
|
|
333
|
+
**Returns:** `ListEnvironmentsResponse` — `{ environments: Environment[] }`
|
|
334
|
+
|
|
335
|
+
Each `Environment` has:
|
|
336
|
+
|
|
337
|
+
| Field | Type | Description |
|
|
338
|
+
|-------|------|-------------|
|
|
339
|
+
| `id` | `string` | Unique identifier |
|
|
340
|
+
| `name` | `string` | Environment display name |
|
|
341
|
+
| `slug` | `string` | URL-safe identifier |
|
|
342
|
+
| `description` | `string \| null` | Description |
|
|
343
|
+
|
|
344
|
+
#### `client.environments.create(org, project, data)`
|
|
345
|
+
|
|
346
|
+
Create a new environment within a project.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const { environment } = await client.environments.create(
|
|
350
|
+
"my-org",
|
|
351
|
+
"my-project",
|
|
352
|
+
{
|
|
353
|
+
name: "QA",
|
|
354
|
+
description: "Quality assurance testing", // optional
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
console.log(environment.slug); // "qa"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Parameters:**
|
|
362
|
+
|
|
363
|
+
| Param | Type | Required | Description |
|
|
364
|
+
|-------|------|----------|-------------|
|
|
365
|
+
| `org` | `string` | Yes | Organization slug |
|
|
366
|
+
| `project` | `string` | Yes | Project slug |
|
|
367
|
+
| `name` | `string` | Yes | Environment display name |
|
|
368
|
+
| `description` | `string` | No | Description |
|
|
369
|
+
|
|
370
|
+
**Returns:** `CreateEnvironmentResponse` — `{ environment: { name, slug } }`
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### Environment Version
|
|
375
|
+
|
|
376
|
+
Track the current version of an environment. Useful for cache invalidation and polling for changes.
|
|
377
|
+
|
|
378
|
+
#### `client.version.get(path, etag?)`
|
|
379
|
+
|
|
380
|
+
Get the current version of an environment. Supports HTTP ETag for conditional requests — pass the previous ETag to check if the version has changed without re-fetching secrets.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// First request — get the current version
|
|
384
|
+
const versionInfo = await client.version.get({
|
|
385
|
+
org: "my-org",
|
|
386
|
+
project: "my-project",
|
|
387
|
+
env: "production",
|
|
388
|
+
});
|
|
389
|
+
console.log(versionInfo.version); // 42
|
|
390
|
+
console.log(versionInfo.updatedAt); // "2026-02-25T12:00:00Z"
|
|
391
|
+
|
|
392
|
+
// Subsequent requests — pass the ETag to check for changes
|
|
393
|
+
const etag = `"v${versionInfo.version}"`;
|
|
394
|
+
const updated = await client.version.get(
|
|
395
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
396
|
+
etag,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (updated === null) {
|
|
400
|
+
console.log("No changes since last check");
|
|
401
|
+
} else {
|
|
402
|
+
console.log(`New version: ${updated.version}`);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Parameters:**
|
|
407
|
+
|
|
408
|
+
| Param | Type | Required | Description |
|
|
409
|
+
|-------|------|----------|-------------|
|
|
410
|
+
| `path` | `EnvPath` | Yes | Organization, project, and environment slugs |
|
|
411
|
+
| `etag` | `string` | No | ETag from a previous response (e.g., `"v42"`) |
|
|
412
|
+
|
|
413
|
+
**Returns:** `GetVersionResponse | null`
|
|
414
|
+
|
|
415
|
+
- Returns `{ version: number, updatedAt: string }` if the version has changed (or no ETag was provided).
|
|
416
|
+
- Returns `null` if the version has not changed (304 Not Modified).
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### Secret Versions
|
|
421
|
+
|
|
422
|
+
View the change history of individual secrets and rollback to previous versions.
|
|
423
|
+
|
|
424
|
+
#### `client.versions.list(path, secretKey, options?)`
|
|
425
|
+
|
|
426
|
+
List the version history for a specific secret key.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const { versions } = await client.versions.list(
|
|
430
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
431
|
+
"DATABASE_URL",
|
|
432
|
+
{ limit: 10, offset: 0 }, // optional pagination
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
for (const v of versions) {
|
|
436
|
+
console.log(`v${v.version} — ${v.changeType} at ${v.createdAt}`);
|
|
437
|
+
}
|
|
438
|
+
// "v5 — updated at 2026-02-25T12:00:00Z"
|
|
439
|
+
// "v3 — created at 2026-02-20T08:00:00Z"
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Parameters:**
|
|
443
|
+
|
|
444
|
+
| Param | Type | Required | Description |
|
|
445
|
+
|-------|------|----------|-------------|
|
|
446
|
+
| `path` | `EnvPath` | Yes | Organization, project, and environment slugs |
|
|
447
|
+
| `secretKey` | `string` | Yes | The secret key name (e.g., `"DATABASE_URL"`) |
|
|
448
|
+
| `options.limit` | `number` | No | Maximum results to return (default: 50) |
|
|
449
|
+
| `options.offset` | `number` | No | Number of results to skip (default: 0) |
|
|
450
|
+
|
|
451
|
+
**Returns:** `ListSecretVersionsResponse` — `{ versions: SecretVersion[] }`
|
|
452
|
+
|
|
453
|
+
Each `SecretVersion` has:
|
|
454
|
+
|
|
455
|
+
| Field | Type | Description |
|
|
456
|
+
|-------|------|-------------|
|
|
457
|
+
| `version` | `number` | Version number |
|
|
458
|
+
| `changeType` | `string` | `"created"`, `"updated"`, or `"rolled_back"` |
|
|
459
|
+
| `changedBy` | `string \| null` | User ID who made the change (null for service tokens) |
|
|
460
|
+
| `comment` | `string \| null` | Optional change comment |
|
|
461
|
+
| `createdAt` | `string` | ISO 8601 timestamp |
|
|
462
|
+
|
|
463
|
+
#### `client.versions.rollback(path, secretKey, targetVersion)`
|
|
464
|
+
|
|
465
|
+
Rollback a secret to a previous version. This creates a new version with the old value.
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
const result = await client.versions.rollback(
|
|
469
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
470
|
+
"DATABASE_URL",
|
|
471
|
+
3, // target version to rollback to
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
console.log(result.ok); // true
|
|
475
|
+
console.log(result.newVersion); // 6 (the newly created version)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Parameters:**
|
|
479
|
+
|
|
480
|
+
| Param | Type | Description |
|
|
481
|
+
|-------|------|-------------|
|
|
482
|
+
| `path` | `EnvPath` | Organization, project, and environment slugs |
|
|
483
|
+
| `secretKey` | `string` | The secret key name |
|
|
484
|
+
| `targetVersion` | `number` | Version number to rollback to |
|
|
485
|
+
|
|
486
|
+
**Returns:** `RollbackSecretResponse` — `{ ok: true, newVersion: number }`
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
### Snapshots
|
|
491
|
+
|
|
492
|
+
Capture and restore complete environment state. Snapshots save the current version of every secret so you can restore them all at once.
|
|
493
|
+
|
|
494
|
+
#### `client.snapshots.list(path)`
|
|
495
|
+
|
|
496
|
+
List all snapshots for an environment.
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
const { snapshots } = await client.snapshots.list({
|
|
500
|
+
org: "my-org",
|
|
501
|
+
project: "my-project",
|
|
502
|
+
env: "production",
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
for (const snap of snapshots) {
|
|
506
|
+
console.log(`${snap.name ?? "Unnamed"} — ${snap.createdAt}`);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Returns:** `ListSnapshotsResponse` — `{ snapshots: Snapshot[] }`
|
|
511
|
+
|
|
512
|
+
Each `Snapshot` has:
|
|
513
|
+
|
|
514
|
+
| Field | Type | Description |
|
|
515
|
+
|-------|------|-------------|
|
|
516
|
+
| `id` | `string` | Unique identifier |
|
|
517
|
+
| `name` | `string \| null` | Optional snapshot name |
|
|
518
|
+
| `description` | `string \| null` | Optional description |
|
|
519
|
+
| `createdBy` | `string \| null` | User ID who created it (null for service tokens) |
|
|
520
|
+
| `createdAt` | `string` | ISO 8601 timestamp |
|
|
521
|
+
|
|
522
|
+
#### `client.snapshots.create(path, data?)`
|
|
523
|
+
|
|
524
|
+
Create a snapshot of the current environment state.
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
const snapshot = await client.snapshots.create(
|
|
528
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
529
|
+
{
|
|
530
|
+
name: "pre-deploy-v2.5", // optional
|
|
531
|
+
description: "Before v2.5 release", // optional
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
console.log(snapshot.id); // "snap_abc123"
|
|
536
|
+
console.log(snapshot.name); // "pre-deploy-v2.5"
|
|
537
|
+
console.log(snapshot.createdAt); // "2026-02-25T12:00:00Z"
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Parameters:**
|
|
541
|
+
|
|
542
|
+
| Param | Type | Required | Description |
|
|
543
|
+
|-------|------|----------|-------------|
|
|
544
|
+
| `path` | `EnvPath` | Yes | Organization, project, and environment slugs |
|
|
545
|
+
| `name` | `string` | No | Snapshot name |
|
|
546
|
+
| `description` | `string` | No | Snapshot description |
|
|
547
|
+
|
|
548
|
+
**Returns:** `CreateSnapshotResponse` — `{ id, name, createdAt }`
|
|
549
|
+
|
|
550
|
+
#### `client.snapshots.restore(path, snapshotId)`
|
|
551
|
+
|
|
552
|
+
Restore an environment to a previous snapshot state. All secrets are restored to their values at the time the snapshot was taken.
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
const result = await client.snapshots.restore(
|
|
556
|
+
{ org: "my-org", project: "my-project", env: "production" },
|
|
557
|
+
"snap_abc123",
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
console.log(result.ok); // true
|
|
561
|
+
console.log(result.restoredCount); // 15 (number of secrets restored)
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**Parameters:**
|
|
565
|
+
|
|
566
|
+
| Param | Type | Description |
|
|
567
|
+
|-------|------|-------------|
|
|
568
|
+
| `path` | `EnvPath` | Organization, project, and environment slugs |
|
|
569
|
+
| `snapshotId` | `string` | ID of the snapshot to restore |
|
|
570
|
+
|
|
571
|
+
**Returns:** `RestoreSnapshotResponse` — `{ ok: true, restoredCount: number }`
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
### Service Tokens
|
|
576
|
+
|
|
577
|
+
Manage machine-to-machine authentication tokens. These endpoints require organization admin or owner access.
|
|
578
|
+
|
|
579
|
+
#### `client.serviceTokens.list(org)`
|
|
580
|
+
|
|
581
|
+
List all service tokens for an organization.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
const { tokens } = await client.serviceTokens.list("my-org");
|
|
585
|
+
|
|
586
|
+
for (const token of tokens) {
|
|
587
|
+
console.log(`${token.name} (${token.scope}, ${token.permission})`);
|
|
588
|
+
console.log(` Active: ${token.is_active}`);
|
|
589
|
+
console.log(` Last used: ${token.last_used_at ?? "never"}`);
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**Returns:** `ListServiceTokensResponse` — `{ tokens: ServiceToken[] }`
|
|
594
|
+
|
|
595
|
+
Each `ServiceToken` has:
|
|
596
|
+
|
|
597
|
+
| Field | Type | Description |
|
|
598
|
+
|-------|------|-------------|
|
|
599
|
+
| `id` | `string` | Unique identifier |
|
|
600
|
+
| `name` | `string` | Token display name |
|
|
601
|
+
| `description` | `string \| null` | Description |
|
|
602
|
+
| `token_prefix` | `string` | First characters of the token for identification |
|
|
603
|
+
| `scope` | `"org" \| "project" \| "environment"` | Access scope |
|
|
604
|
+
| `project_id` | `string \| null` | Scoped project (if applicable) |
|
|
605
|
+
| `environment_id` | `string \| null` | Scoped environment (if applicable) |
|
|
606
|
+
| `permission` | `"read" \| "read_write"` | Permission level |
|
|
607
|
+
| `expires_at` | `string \| null` | Expiration date (ISO 8601) or null for no expiration |
|
|
608
|
+
| `last_used_at` | `string \| null` | Last usage timestamp |
|
|
609
|
+
| `is_active` | `boolean` | Whether the token is active |
|
|
610
|
+
| `created_at` | `string` | Creation timestamp |
|
|
611
|
+
| `created_by_email` | `string` | Email of the user who created it |
|
|
612
|
+
| `created_by_name` | `string \| null` | Name of the user who created it |
|
|
613
|
+
|
|
614
|
+
#### `client.serviceTokens.create(org, data)`
|
|
615
|
+
|
|
616
|
+
Create a new service token. The raw token value is returned only once — store it securely.
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
const result = await client.serviceTokens.create("my-org", {
|
|
620
|
+
name: "CI/CD Pipeline",
|
|
621
|
+
description: "GitHub Actions deployment token", // optional
|
|
622
|
+
scope: "environment", // "org" | "project" | "environment"
|
|
623
|
+
projectId: "project-uuid", // required for "project" or "environment" scope
|
|
624
|
+
environmentId: "env-uuid", // required for "environment" scope
|
|
625
|
+
permission: "read_only", // "read" | "read_write"
|
|
626
|
+
expiresAt: "2026-12-31T23:59:59Z", // optional ISO 8601 date
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
console.log(result.token); // "envshed_svc_..." — save this, it won't be shown again
|
|
630
|
+
console.log(result.id); // token ID for future management
|
|
631
|
+
console.log(result.name); // "CI/CD Pipeline"
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Parameters:**
|
|
635
|
+
|
|
636
|
+
| Param | Type | Required | Description |
|
|
637
|
+
|-------|------|----------|-------------|
|
|
638
|
+
| `org` | `string` | Yes | Organization slug |
|
|
639
|
+
| `name` | `string` | Yes | Token display name |
|
|
640
|
+
| `description` | `string` | No | Description |
|
|
641
|
+
| `scope` | `"org" \| "project" \| "environment"` | No | Access scope |
|
|
642
|
+
| `projectId` | `string` | Conditional | Required if scope is `"project"` or `"environment"` |
|
|
643
|
+
| `environmentId` | `string` | Conditional | Required if scope is `"environment"` |
|
|
644
|
+
| `permission` | `"read" \| "read_write"` | No | Permission level |
|
|
645
|
+
| `expiresAt` | `string` | No | Expiration date (ISO 8601) |
|
|
646
|
+
|
|
647
|
+
**Returns:** `CreateServiceTokenResponse` — `{ token, id, name }`
|
|
648
|
+
|
|
649
|
+
#### `client.serviceTokens.delete(org, tokenId)`
|
|
650
|
+
|
|
651
|
+
Revoke a service token. This action is immediate and irreversible.
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
const result = await client.serviceTokens.delete("my-org", "token-id-123");
|
|
655
|
+
console.log(result.ok); // true
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Parameters:**
|
|
659
|
+
|
|
660
|
+
| Param | Type | Description |
|
|
661
|
+
|-------|------|-------------|
|
|
662
|
+
| `org` | `string` | Organization slug |
|
|
663
|
+
| `tokenId` | `string` | ID of the token to revoke |
|
|
664
|
+
|
|
665
|
+
**Returns:** `DeleteServiceTokenResponse` — `{ ok: true }`
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## Error Handling
|
|
670
|
+
|
|
671
|
+
The SDK throws typed errors that you can catch and inspect.
|
|
672
|
+
|
|
673
|
+
### `EnvshedError`
|
|
674
|
+
|
|
675
|
+
Thrown when the API returns an HTTP error response (4xx or 5xx).
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
import { EnvshedClient, EnvshedError } from "@envshed/node";
|
|
679
|
+
|
|
680
|
+
const client = new EnvshedClient({ token: "envshed_..." });
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
await client.secrets.get({
|
|
684
|
+
org: "my-org",
|
|
685
|
+
project: "my-project",
|
|
686
|
+
env: "nonexistent",
|
|
687
|
+
});
|
|
688
|
+
} catch (err) {
|
|
689
|
+
if (err instanceof EnvshedError) {
|
|
690
|
+
console.log(err.status); // 404
|
|
691
|
+
console.log(err.apiMessage); // "Environment not found"
|
|
692
|
+
console.log(err.method); // "GET"
|
|
693
|
+
console.log(err.path); // "/secrets/my-org/my-project/nonexistent"
|
|
694
|
+
|
|
695
|
+
// Convenience getters
|
|
696
|
+
if (err.isNotFound) {
|
|
697
|
+
console.log("Resource does not exist");
|
|
698
|
+
} else if (err.isUnauthorized) {
|
|
699
|
+
console.log("Invalid or expired token");
|
|
700
|
+
} else if (err.isForbidden) {
|
|
701
|
+
console.log("Insufficient permissions");
|
|
702
|
+
} else if (err.isSubscriptionRequired) {
|
|
703
|
+
console.log("Organization subscription required");
|
|
704
|
+
} else if (err.isRetryable) {
|
|
705
|
+
console.log("Server error — retries were exhausted");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Properties:**
|
|
712
|
+
|
|
713
|
+
| Property | Type | Description |
|
|
714
|
+
|----------|------|-------------|
|
|
715
|
+
| `status` | `number` | HTTP status code |
|
|
716
|
+
| `apiMessage` | `string` | Error message from the API |
|
|
717
|
+
| `method` | `string` | HTTP method (`GET`, `POST`, `PUT`, `DELETE`) |
|
|
718
|
+
| `path` | `string` | API path that was called |
|
|
719
|
+
| `isUnauthorized` | `boolean` | `true` if status is 401 |
|
|
720
|
+
| `isForbidden` | `boolean` | `true` if status is 403 |
|
|
721
|
+
| `isNotFound` | `boolean` | `true` if status is 404 |
|
|
722
|
+
| `isSubscriptionRequired` | `boolean` | `true` if status is 402 |
|
|
723
|
+
| `isRetryable` | `boolean` | `true` if status is 5xx |
|
|
724
|
+
|
|
725
|
+
### `EnvshedNetworkError`
|
|
726
|
+
|
|
727
|
+
Thrown when a network-level failure occurs (DNS resolution failure, connection refused, timeout, etc.). These errors are always considered retryable and will be retried according to your retry configuration before being thrown.
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
import { EnvshedClient, EnvshedNetworkError } from "@envshed/node";
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
await client.me();
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (err instanceof EnvshedNetworkError) {
|
|
736
|
+
console.log(err.message); // "Envshed network error: fetch failed"
|
|
737
|
+
console.log(err.method); // "GET"
|
|
738
|
+
console.log(err.path); // "/me"
|
|
739
|
+
console.log(err.cause); // Original Error object
|
|
740
|
+
console.log(err.isRetryable); // true (always)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**Properties:**
|
|
746
|
+
|
|
747
|
+
| Property | Type | Description |
|
|
748
|
+
|----------|------|-------------|
|
|
749
|
+
| `method` | `string` | HTTP method |
|
|
750
|
+
| `path` | `string` | API path that was called |
|
|
751
|
+
| `cause` | `Error` | The original network error |
|
|
752
|
+
| `isRetryable` | `boolean` | Always `true` |
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Retry Strategy
|
|
757
|
+
|
|
758
|
+
The SDK automatically retries failed requests for transient failures. The retry behavior is fully customizable.
|
|
759
|
+
|
|
760
|
+
### Default behavior
|
|
761
|
+
|
|
762
|
+
- **5xx server errors** — retried up to `maxRetries` times
|
|
763
|
+
- **Network errors** (DNS, connection refused, timeout) — retried up to `maxRetries` times
|
|
764
|
+
- **4xx client errors** — never retried (these are deterministic)
|
|
765
|
+
|
|
766
|
+
Retries use **exponential backoff with jitter**:
|
|
767
|
+
|
|
768
|
+
```
|
|
769
|
+
delay = min(initialDelayMs * 2^(attempt - 1), maxDelayMs) +/- 25% jitter
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
With default settings (`maxRetries: 3, initialDelayMs: 1000, maxDelayMs: 10000`), retry delays are approximately:
|
|
773
|
+
|
|
774
|
+
| Attempt | Base Delay | With Jitter |
|
|
775
|
+
|---------|-----------|-------------|
|
|
776
|
+
| 1st retry | 1000ms | 750–1250ms |
|
|
777
|
+
| 2nd retry | 2000ms | 1500–2500ms |
|
|
778
|
+
| 3rd retry | 4000ms | 3000–5000ms |
|
|
779
|
+
|
|
780
|
+
To disable retries entirely:
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
const client = new EnvshedClient({
|
|
784
|
+
token: "envshed_...",
|
|
785
|
+
retry: { maxRetries: 0 },
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Backoff strategies
|
|
790
|
+
|
|
791
|
+
Choose between built-in strategies or provide your own function.
|
|
792
|
+
|
|
793
|
+
**Exponential (default)** — delay doubles each attempt:
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
const client = new EnvshedClient({
|
|
797
|
+
token: "envshed_...",
|
|
798
|
+
retry: {
|
|
799
|
+
backoff: "exponential", // initialDelayMs * 2^(attempt-1)
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Linear** — constant delay between retries:
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
const client = new EnvshedClient({
|
|
808
|
+
token: "envshed_...",
|
|
809
|
+
retry: {
|
|
810
|
+
backoff: "linear", // initialDelayMs for every attempt
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
**Custom function** — return the delay in milliseconds (still capped by `maxDelayMs`):
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
const client = new EnvshedClient({
|
|
819
|
+
token: "envshed_...",
|
|
820
|
+
retry: {
|
|
821
|
+
initialDelayMs: 100,
|
|
822
|
+
maxDelayMs: 30000,
|
|
823
|
+
backoff: (attempt, initialDelayMs) => {
|
|
824
|
+
// Cubic backoff: 100ms, 800ms, 2700ms, ...
|
|
825
|
+
return initialDelayMs * Math.pow(attempt, 3);
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
The `BackoffFunction` signature:
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
type BackoffFunction = (attempt: number, initialDelayMs: number) => number;
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
- `attempt` — 1-based attempt number (1 for the first retry, 2 for the second, etc.)
|
|
838
|
+
- `initialDelayMs` — the configured `initialDelayMs` value
|
|
839
|
+
- Return value is capped by `maxDelayMs` and has +/- 25% jitter applied automatically
|
|
840
|
+
|
|
841
|
+
### Custom retry condition (`shouldRetry`)
|
|
842
|
+
|
|
843
|
+
Override which errors trigger a retry. Receives the error and the current attempt number.
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
import { EnvshedClient, EnvshedError } from "@envshed/node";
|
|
847
|
+
|
|
848
|
+
const client = new EnvshedClient({
|
|
849
|
+
token: "envshed_...",
|
|
850
|
+
retry: {
|
|
851
|
+
maxRetries: 5,
|
|
852
|
+
shouldRetry: (error, attempt) => {
|
|
853
|
+
// Retry 429 (rate limited) in addition to the default 5xx
|
|
854
|
+
if (error instanceof EnvshedError) {
|
|
855
|
+
return error.status >= 500 || error.status === 429;
|
|
856
|
+
}
|
|
857
|
+
// Always retry network errors
|
|
858
|
+
return true;
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
The `ShouldRetryFunction` signature:
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
type ShouldRetryFunction = (error: Error, attempt: number) => boolean;
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
- `error` — an `EnvshedError` (HTTP error) or `EnvshedNetworkError` (network failure)
|
|
871
|
+
- `attempt` — 1-based attempt number
|
|
872
|
+
- Return `true` to retry, `false` to throw immediately
|
|
873
|
+
|
|
874
|
+
### Retry callback (`onRetry`)
|
|
875
|
+
|
|
876
|
+
Hook into each retry for logging, metrics, or monitoring.
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
const client = new EnvshedClient({
|
|
880
|
+
token: "envshed_...",
|
|
881
|
+
retry: {
|
|
882
|
+
onRetry: (error, attempt) => {
|
|
883
|
+
console.warn(`[envshed] Retry attempt ${attempt}:`, error.message);
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
The `OnRetryFunction` signature:
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
type OnRetryFunction = (error: Error, attempt: number) => void;
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
- Called **before** the delay/sleep for each retry
|
|
896
|
+
- Not called on the initial request or when retries are disabled
|
|
897
|
+
|
|
898
|
+
### Full example: custom retry strategy
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
import { EnvshedClient, EnvshedError, EnvshedNetworkError } from "@envshed/node";
|
|
902
|
+
|
|
903
|
+
const client = new EnvshedClient({
|
|
904
|
+
token: process.env.ENVSHED_TOKEN!,
|
|
905
|
+
retry: {
|
|
906
|
+
maxRetries: 5,
|
|
907
|
+
initialDelayMs: 200,
|
|
908
|
+
maxDelayMs: 30000,
|
|
909
|
+
|
|
910
|
+
// Linear backoff
|
|
911
|
+
backoff: "linear",
|
|
912
|
+
|
|
913
|
+
// Retry on 429, 5xx, and network errors
|
|
914
|
+
shouldRetry: (error, attempt) => {
|
|
915
|
+
if (error instanceof EnvshedError) {
|
|
916
|
+
return error.status === 429 || error.status >= 500;
|
|
917
|
+
}
|
|
918
|
+
return true; // network errors
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
// Log retries
|
|
922
|
+
onRetry: (error, attempt) => {
|
|
923
|
+
const status = error instanceof EnvshedError ? error.status : "network";
|
|
924
|
+
console.warn(`[envshed] Retry ${attempt}/5 (${status}): ${error.message}`);
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
## Common Patterns
|
|
933
|
+
|
|
934
|
+
### Load secrets into `process.env`
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
import { EnvshedClient } from "@envshed/node";
|
|
938
|
+
|
|
939
|
+
const client = new EnvshedClient({ token: process.env.ENVSHED_TOKEN! });
|
|
940
|
+
|
|
941
|
+
const { secrets } = await client.secrets.get({
|
|
942
|
+
org: "my-org",
|
|
943
|
+
project: "my-project",
|
|
944
|
+
env: "production",
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// Inject into process.env
|
|
948
|
+
Object.assign(process.env, secrets);
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### Sync secrets from a `.env` file
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
import { readFileSync } from "node:fs";
|
|
955
|
+
import { EnvshedClient } from "@envshed/node";
|
|
956
|
+
|
|
957
|
+
const client = new EnvshedClient({ token: process.env.ENVSHED_TOKEN! });
|
|
958
|
+
|
|
959
|
+
// Parse a .env file
|
|
960
|
+
const envContent = readFileSync(".env", "utf-8");
|
|
961
|
+
const secrets: Record<string, string> = {};
|
|
962
|
+
for (const line of envContent.split("\n")) {
|
|
963
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
964
|
+
if (match) {
|
|
965
|
+
secrets[match[1]] = match[2];
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Push to Envshed
|
|
970
|
+
await client.secrets.set(
|
|
971
|
+
{ org: "my-org", project: "my-project", env: "development" },
|
|
972
|
+
secrets,
|
|
973
|
+
);
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### Poll for environment changes
|
|
977
|
+
|
|
978
|
+
```typescript
|
|
979
|
+
import { EnvshedClient } from "@envshed/node";
|
|
980
|
+
|
|
981
|
+
const client = new EnvshedClient({ token: process.env.ENVSHED_TOKEN! });
|
|
982
|
+
const envPath = { org: "my-org", project: "my-project", env: "production" };
|
|
983
|
+
|
|
984
|
+
let lastEtag: string | undefined;
|
|
985
|
+
|
|
986
|
+
setInterval(async () => {
|
|
987
|
+
const result = await client.version.get(envPath, lastEtag);
|
|
988
|
+
|
|
989
|
+
if (result === null) {
|
|
990
|
+
// No changes
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
lastEtag = `"v${result.version}"`;
|
|
995
|
+
console.log(`Environment updated to version ${result.version}`);
|
|
996
|
+
|
|
997
|
+
// Re-fetch secrets
|
|
998
|
+
const { secrets } = await client.secrets.get(envPath);
|
|
999
|
+
Object.assign(process.env, secrets);
|
|
1000
|
+
}, 30_000); // Poll every 30 seconds
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
### Create a pre-deploy snapshot
|
|
1004
|
+
|
|
1005
|
+
```typescript
|
|
1006
|
+
import { EnvshedClient } from "@envshed/node";
|
|
1007
|
+
|
|
1008
|
+
const client = new EnvshedClient({ token: process.env.ENVSHED_TOKEN! });
|
|
1009
|
+
const envPath = { org: "my-org", project: "my-project", env: "production" };
|
|
1010
|
+
|
|
1011
|
+
// Snapshot before deploying
|
|
1012
|
+
const snapshot = await client.snapshots.create(envPath, {
|
|
1013
|
+
name: `pre-deploy-${new Date().toISOString()}`,
|
|
1014
|
+
description: "Automatic snapshot before deployment",
|
|
1015
|
+
});
|
|
1016
|
+
console.log(`Snapshot created: ${snapshot.id}`);
|
|
1017
|
+
|
|
1018
|
+
// ... deploy ...
|
|
1019
|
+
|
|
1020
|
+
// If something goes wrong, restore
|
|
1021
|
+
await client.snapshots.restore(envPath, snapshot.id);
|
|
1022
|
+
console.log("Rolled back to pre-deploy state");
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
## TypeScript
|
|
1028
|
+
|
|
1029
|
+
The SDK is written in TypeScript and exports all types. You can import any type you need:
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
import type {
|
|
1033
|
+
EnvshedClientOptions,
|
|
1034
|
+
RetryOptions,
|
|
1035
|
+
BackoffFunction,
|
|
1036
|
+
ShouldRetryFunction,
|
|
1037
|
+
OnRetryFunction,
|
|
1038
|
+
EnvPath,
|
|
1039
|
+
GetSecretsResponse,
|
|
1040
|
+
SetSecretsResponse,
|
|
1041
|
+
Organization,
|
|
1042
|
+
Project,
|
|
1043
|
+
Environment,
|
|
1044
|
+
SecretVersion,
|
|
1045
|
+
Snapshot,
|
|
1046
|
+
ServiceToken,
|
|
1047
|
+
MeResponse,
|
|
1048
|
+
} from "@envshed/node";
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
---
|
|
1052
|
+
|
|
1053
|
+
## License
|
|
1054
|
+
|
|
1055
|
+
MIT
|