cf-envsync 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 +518 -0
- package/dist/index.js +14635 -0
- package/package.json +40 -0
- package/src/define-config.ts +30 -0
- package/src/types/config.ts +65 -0
- package/src/types/env.ts +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br />
|
|
3
|
+
<code>.env</code> → <strong>Cloudflare Workers</strong> → done.
|
|
4
|
+
<br />
|
|
5
|
+
<br />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<h1 align="center">envsync</h1>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
One <code>.env</code> file. Every Worker. Every environment.<br />
|
|
12
|
+
No SaaS. No dashboard. Just your repo.
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="#quick-start">Quick Start</a> | <a href="#commands">Commands</a> | <a href="#configuration">Config</a> | <a href="#why">Why?</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
.env.{environment} ──→ envsync ──→ Cloudflare Workers secrets
|
|
23
|
+
+ │ (per worker, per env)
|
|
24
|
+
.env.local ──→ │
|
|
25
|
+
(per-developer) ├──→ .dev.vars for each app
|
|
26
|
+
│ (local dev)
|
|
27
|
+
└──→ validation
|
|
28
|
+
(nothing missing)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## The Problem
|
|
34
|
+
|
|
35
|
+
If you're building on **Cloudflare Workers** with **dotenvx** encryption in a **monorepo**, you know the pain:
|
|
36
|
+
|
|
37
|
+
- **dotenvx breaks Git** — Same plaintext, different ciphertext every time. Two devs touch `.env` = guaranteed merge conflict.
|
|
38
|
+
- **Three layers that don't sync** — Vite reads `.env`, wrangler reads `.dev.vars`, production reads from the dashboard. Forget to update one? Silent failure.
|
|
39
|
+
- **N Workers x M Environments x manual labor** — `wrangler secret put` one key at a time, per worker, per environment.
|
|
40
|
+
- **Per-developer secrets** — OAuth callback URLs differ per dev tunnel. No way to enforce they're set.
|
|
41
|
+
- **No way to verify what's deployed** — "Is production using the new key or the old one?" Push and pray.
|
|
42
|
+
|
|
43
|
+
Every existing tool solves one piece. **envsync connects them all.**
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bun add -d cf-envsync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Initialize (scans wrangler.jsonc files in monorepos)
|
|
55
|
+
envsync init --monorepo
|
|
56
|
+
|
|
57
|
+
# Generate .dev.vars for local development
|
|
58
|
+
envsync dev
|
|
59
|
+
|
|
60
|
+
# Push secrets to staging
|
|
61
|
+
envsync push staging
|
|
62
|
+
|
|
63
|
+
# Validate nothing is missing before deploying
|
|
64
|
+
envsync validate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Requirements
|
|
68
|
+
|
|
69
|
+
- [Node.js](https://nodejs.org) >= 18 or [Bun](https://bun.sh)
|
|
70
|
+
- [wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI (peer dependency, for push/pull/diff)
|
|
71
|
+
- [dotenvx](https://dotenvx.com) (optional, for encryption)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
### `envsync dev` — Generate `.dev.vars`
|
|
78
|
+
|
|
79
|
+
The command you'll use most. Merges `.env` + `.env.local` and writes `.dev.vars` for each app.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
envsync dev # All apps
|
|
83
|
+
envsync dev api # Just api
|
|
84
|
+
envsync dev api web # Multiple apps
|
|
85
|
+
envsync dev --env staging # Use staging values for local dev
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
$ envsync dev
|
|
90
|
+
|
|
91
|
+
apps/api/.dev.vars
|
|
92
|
+
├ DATABASE_URL ← .env
|
|
93
|
+
├ TWITCH_CLIENT_SECRET ← .env (shared)
|
|
94
|
+
├ TWITCH_CLIENT_ID ← .env (shared)
|
|
95
|
+
├ JWT_SECRET ← .env (shared)
|
|
96
|
+
├ API_URL ← .env
|
|
97
|
+
└ OAUTH_REDIRECT_URL ← .env.local (per-dev override)
|
|
98
|
+
|
|
99
|
+
apps/web/.dev.vars
|
|
100
|
+
├ AUTH_SECRET ← .env
|
|
101
|
+
├ VITE_API_URL ← .env
|
|
102
|
+
└ VITE_OAUTH_REDIRECT_URL ← .env.local (per-dev override)
|
|
103
|
+
|
|
104
|
+
⚠ Missing in .env.local: DEV_TUNNEL_URL (required per-dev override)
|
|
105
|
+
→ echo "DEV_TUNNEL_URL=https://your-tunnel.example.com" >> .env.local
|
|
106
|
+
|
|
107
|
+
Done!
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Every key shows exactly where its value came from. Missing per-dev overrides are caught immediately.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `envsync push` — Deploy secrets
|
|
115
|
+
|
|
116
|
+
Push secrets to Cloudflare Workers via `wrangler secret bulk`. One command, all workers.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
envsync push staging # All apps → staging workers
|
|
120
|
+
envsync push production # All apps → production workers
|
|
121
|
+
envsync push staging api # Just api's staging worker
|
|
122
|
+
envsync push production --shared # Only shared secrets (JWT_SECRET, etc.)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
$ envsync push staging --dry-run
|
|
127
|
+
|
|
128
|
+
Pushing secrets for api → my-app-api-staging (staging)...
|
|
129
|
+
Would push 4 secrets to worker "my-app-api-staging"
|
|
130
|
+
DATABASE_URL
|
|
131
|
+
TWITCH_CLIENT_ID (shared)
|
|
132
|
+
TWITCH_CLIENT_SECRET (shared)
|
|
133
|
+
JWT_SECRET (shared)
|
|
134
|
+
|
|
135
|
+
Pushing secrets for web → my-app-web-staging (staging)...
|
|
136
|
+
Would push 1 secrets to worker "my-app-web-staging"
|
|
137
|
+
AUTH_SECRET
|
|
138
|
+
|
|
139
|
+
Done!
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `--dry-run` to preview. Use `--force` to skip confirmation prompts (CI-friendly).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `envsync diff` — Compare environments
|
|
147
|
+
|
|
148
|
+
Two modes: **local vs remote** and **env vs env**.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Local .env.production vs what's actually on Cloudflare
|
|
152
|
+
envsync diff production
|
|
153
|
+
envsync diff production api
|
|
154
|
+
|
|
155
|
+
# Compare two environments side-by-side
|
|
156
|
+
envsync diff staging production
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
$ envsync diff staging production
|
|
161
|
+
|
|
162
|
+
stream-collector
|
|
163
|
+
TWITCH_CLIENT_ID stag**** prod**** ✔ expected
|
|
164
|
+
TWITCH_CLIENT_SECRET stag**** prod**** ✔ expected
|
|
165
|
+
YOUTUBE_API_KEY stag**** (missing) ✘ missing in production!
|
|
166
|
+
|
|
167
|
+
1 key(s) missing
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Catch missing keys before they break production.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### `envsync validate` — Catch missing keys
|
|
175
|
+
|
|
176
|
+
Checks all apps across all environments against `.env.example`.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
envsync validate # All environments, all apps
|
|
180
|
+
envsync validate staging # Just staging
|
|
181
|
+
envsync validate staging api # Just api in staging
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
$ envsync validate
|
|
186
|
+
|
|
187
|
+
Checking against .env.example...
|
|
188
|
+
|
|
189
|
+
local
|
|
190
|
+
✔ api: all 8 keys present
|
|
191
|
+
✔ web: all 6 keys present
|
|
192
|
+
✔ stream-collector: all 6 keys present
|
|
193
|
+
|
|
194
|
+
staging
|
|
195
|
+
✔ api: all 6 keys present
|
|
196
|
+
✔ web: all 3 keys present
|
|
197
|
+
✔ stream-collector: all 4 keys present
|
|
198
|
+
|
|
199
|
+
production
|
|
200
|
+
✔ api: all 6 keys present
|
|
201
|
+
✔ web: all 3 keys present
|
|
202
|
+
✘ stream-collector: 3/4 keys
|
|
203
|
+
missing: YOUTUBE_API_KEY
|
|
204
|
+
|
|
205
|
+
⚠ 1 environment(s) have issues
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Exits with code 1 on failure — plug it into CI.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### `envsync pull` — Scaffold from remote
|
|
213
|
+
|
|
214
|
+
Pull secret key names from Cloudflare and scaffold empty entries in your local `.env` file. (Values are not available via the API — only key names.)
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
envsync pull staging
|
|
218
|
+
envsync pull production api
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### `envsync list` — See the full picture
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
envsync list # Summary table
|
|
227
|
+
envsync list api --keys # Detailed key list for one app
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
$ envsync list
|
|
232
|
+
|
|
233
|
+
App local staging production
|
|
234
|
+
──────────────── ───────────────── ─────────────────────────── ───────────────────
|
|
235
|
+
api (dev) my-app-api-staging my-app-api
|
|
236
|
+
4 secrets, 2 vars 4 secrets, 2 vars 4 secrets, 2 vars
|
|
237
|
+
|
|
238
|
+
web (dev) my-app-web-staging my-app-web
|
|
239
|
+
1 secret, 2 vars 1 secret, 2 vars 1 secret, 2 vars
|
|
240
|
+
|
|
241
|
+
stream-collector (dev) my-app-collector-staging my-app-collector
|
|
242
|
+
4 secrets 4 secrets 3 secrets
|
|
243
|
+
|
|
244
|
+
Shared secrets (3): JWT_SECRET, TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET
|
|
245
|
+
Per-dev overrides (local only): OAUTH_REDIRECT_URL, DEV_TUNNEL_URL, VITE_OAUTH_REDIRECT_URL
|
|
246
|
+
|
|
247
|
+
.env files status:
|
|
248
|
+
├ .env ✔ (11 keys)
|
|
249
|
+
├ .env.staging ✔ (11 keys)
|
|
250
|
+
├ .env.production ✔ (10 keys)
|
|
251
|
+
└ .env.local ✔ (3 overrides)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### `envsync init` — Project setup
|
|
257
|
+
|
|
258
|
+
Interactive setup that scans your repo and generates everything.
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
envsync init # Single project
|
|
262
|
+
envsync init --monorepo # Scans for wrangler.jsonc files
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
What it does:
|
|
266
|
+
- Scans `wrangler.jsonc` files to discover workers and environments
|
|
267
|
+
- Detects shared secrets across apps
|
|
268
|
+
- Creates `envsync.config.ts`, `.env.example`, and empty `.env.{environment}` files
|
|
269
|
+
- Adds `.env.local`, `.env.keys`, `**/.dev.vars` to `.gitignore`
|
|
270
|
+
- Registers the custom Git merge driver in `.gitattributes`
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### `envsync normalize` — Sort keys
|
|
275
|
+
|
|
276
|
+
Alphabetically sorts keys in all `.env*` files. Reduces diff noise, prevents merge conflicts.
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
envsync normalize # All .env* files recursively
|
|
280
|
+
envsync normalize .env.staging # Specific file
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### `envsync merge` — Git merge driver
|
|
286
|
+
|
|
287
|
+
A 3-way merge driver that understands dotenvx encryption. Registered automatically by `envsync init`.
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
# .gitattributes (auto-generated)
|
|
291
|
+
.env merge=envsync
|
|
292
|
+
.env.* merge=envsync
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
How it works:
|
|
296
|
+
|
|
297
|
+
1. Decrypts all three versions (base, ours, theirs)
|
|
298
|
+
2. 3-way merge at the **key level** — not the encrypted ciphertext
|
|
299
|
+
3. Only real conflicts get conflict markers
|
|
300
|
+
4. Re-encrypts the merged result
|
|
301
|
+
|
|
302
|
+
No more fake conflicts from identical values with different ciphertext.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Configuration
|
|
307
|
+
|
|
308
|
+
### `envsync.config.ts`
|
|
309
|
+
|
|
310
|
+
The recommended way to configure envsync. Full type checking, autocomplete, and comments.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { defineConfig } from "cf-envsync";
|
|
314
|
+
|
|
315
|
+
export default defineConfig({
|
|
316
|
+
environments: ["local", "staging", "production"],
|
|
317
|
+
|
|
318
|
+
envFiles: {
|
|
319
|
+
pattern: ".env.{env}", // local → .env, staging → .env.staging
|
|
320
|
+
local: ".env.local", // per-developer overrides (gitignored)
|
|
321
|
+
perApp: true, // allow apps/api/.env.staging etc.
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
encryption: "dotenvx",
|
|
325
|
+
|
|
326
|
+
apps: {
|
|
327
|
+
api: {
|
|
328
|
+
path: "apps/api",
|
|
329
|
+
workers: {
|
|
330
|
+
staging: "my-api-staging",
|
|
331
|
+
production: "my-api",
|
|
332
|
+
},
|
|
333
|
+
secrets: ["DATABASE_URL", "JWT_SECRET"],
|
|
334
|
+
vars: ["API_URL", "ENVIRONMENT"],
|
|
335
|
+
},
|
|
336
|
+
web: {
|
|
337
|
+
path: "apps/web",
|
|
338
|
+
workers: {
|
|
339
|
+
staging: "my-web-staging",
|
|
340
|
+
production: "my-web",
|
|
341
|
+
},
|
|
342
|
+
secrets: ["AUTH_SECRET"],
|
|
343
|
+
vars: ["VITE_API_URL", "VITE_APP_URL"],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
shared: ["JWT_SECRET"],
|
|
348
|
+
|
|
349
|
+
local: {
|
|
350
|
+
overrides: ["DEV_TUNNEL_URL"],
|
|
351
|
+
perApp: {
|
|
352
|
+
api: ["OAUTH_REDIRECT_URL"],
|
|
353
|
+
web: ["VITE_OAUTH_REDIRECT_URL"],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
<details>
|
|
360
|
+
<summary><strong>Also works with plain JSON</strong></summary>
|
|
361
|
+
|
|
362
|
+
`envsync.json` and `envsync.jsonc` are also supported:
|
|
363
|
+
|
|
364
|
+
```jsonc
|
|
365
|
+
{
|
|
366
|
+
"environments": ["local", "staging", "production"],
|
|
367
|
+
"envFiles": {
|
|
368
|
+
"pattern": ".env.{env}",
|
|
369
|
+
"local": ".env.local",
|
|
370
|
+
"perApp": true
|
|
371
|
+
},
|
|
372
|
+
"encryption": "dotenvx",
|
|
373
|
+
"apps": {
|
|
374
|
+
"api": {
|
|
375
|
+
"path": "apps/api",
|
|
376
|
+
"workers": { "staging": "my-api-staging", "production": "my-api" },
|
|
377
|
+
"secrets": ["DATABASE_URL", "JWT_SECRET"],
|
|
378
|
+
"vars": ["API_URL", "ENVIRONMENT"]
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
</details>
|
|
385
|
+
|
|
386
|
+
<details>
|
|
387
|
+
<summary><strong>JSDoc (for .js configs)</strong></summary>
|
|
388
|
+
|
|
389
|
+
If you prefer plain JavaScript, use JSDoc for type checking:
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
// envsync.config.js
|
|
393
|
+
/** @type {import("cf-envsync").EnvSyncConfig} */
|
|
394
|
+
export default {
|
|
395
|
+
environments: ["local", "staging", "production"],
|
|
396
|
+
// ...
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
</details>
|
|
401
|
+
|
|
402
|
+
<details>
|
|
403
|
+
<summary><strong>Config reference</strong></summary>
|
|
404
|
+
|
|
405
|
+
| Field | Type | Description |
|
|
406
|
+
|-------|------|-------------|
|
|
407
|
+
| `environments` | `string[]` | Available environments |
|
|
408
|
+
| `envFiles.pattern` | `string` | File naming pattern. `{env}` is replaced. `local` falls back to `.env` |
|
|
409
|
+
| `envFiles.local` | `string` | Per-developer override file (gitignored) |
|
|
410
|
+
| `envFiles.perApp` | `boolean` | Allow per-app `.env.{env}` files for app-specific overrides |
|
|
411
|
+
| `encryption` | `"dotenvx" \| "none"` | Encryption method for `.env` files |
|
|
412
|
+
| `apps.{name}.path` | `string` | Path to app directory relative to project root |
|
|
413
|
+
| `apps.{name}.workers` | `Record<string, string>` | Worker name per environment |
|
|
414
|
+
| `apps.{name}.secrets` | `string[]` | Secret keys pushed via `wrangler secret bulk` |
|
|
415
|
+
| `apps.{name}.vars` | `string[]` | Non-secret env vars (not pushed as secrets) |
|
|
416
|
+
| `shared` | `string[]` | Keys with the same value across multiple apps |
|
|
417
|
+
| `local.overrides` | `string[]` | Keys each developer must set in `.env.local` |
|
|
418
|
+
| `local.perApp` | `Record<string, string[]>` | Per-app developer override keys |
|
|
419
|
+
|
|
420
|
+
</details>
|
|
421
|
+
|
|
422
|
+
Config file search order: `envsync.config.ts` > `.js` > `.mjs` > `envsync.json` > `envsync.jsonc`
|
|
423
|
+
|
|
424
|
+
### File structure
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
project/
|
|
428
|
+
├── envsync.config.ts # Config (committed)
|
|
429
|
+
│
|
|
430
|
+
├── .env # Local shared secrets (encrypted, committed)
|
|
431
|
+
├── .env.staging # Staging secrets (encrypted, committed)
|
|
432
|
+
├── .env.production # Production secrets (encrypted, committed)
|
|
433
|
+
├── .env.local # Per-developer overrides (gitignored)
|
|
434
|
+
├── .env.example # Key reference (committed)
|
|
435
|
+
├── .env.keys # dotenvx private keys (gitignored)
|
|
436
|
+
│
|
|
437
|
+
├── apps/
|
|
438
|
+
│ ├── api/
|
|
439
|
+
│ │ ├── wrangler.jsonc
|
|
440
|
+
│ │ ├── .dev.vars # ← generated by envsync dev
|
|
441
|
+
│ │ └── .env.staging # [optional] api-specific staging overrides
|
|
442
|
+
│ ├── web/
|
|
443
|
+
│ │ ├── wrangler.jsonc
|
|
444
|
+
│ │ └── .dev.vars # ← generated
|
|
445
|
+
│ └── stream-collector/
|
|
446
|
+
│ ├── wrangler.jsonc
|
|
447
|
+
│ ├── .dev.vars # ← generated
|
|
448
|
+
│ └── .env # app-specific secrets (YOUTUBE_API_KEY, etc.)
|
|
449
|
+
│
|
|
450
|
+
└── .gitignore # .env.local, .env.keys, **/.dev.vars
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Merge priority
|
|
454
|
+
|
|
455
|
+
Values are merged in this order (last wins):
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
root .env.{env} → app .env.{env} → .env.local (local env only)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Single project
|
|
464
|
+
|
|
465
|
+
Works the same way. Just one app with `path: "."`:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { defineConfig } from "cf-envsync";
|
|
469
|
+
|
|
470
|
+
export default defineConfig({
|
|
471
|
+
environments: ["local", "staging", "production"],
|
|
472
|
+
envFiles: { pattern: ".env.{env}", local: ".env.local", perApp: false },
|
|
473
|
+
encryption: "dotenvx",
|
|
474
|
+
apps: {
|
|
475
|
+
default: {
|
|
476
|
+
path: ".",
|
|
477
|
+
workers: { staging: "my-worker-staging", production: "my-worker" },
|
|
478
|
+
secrets: ["DATABASE_URL", "API_KEY"],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Why?
|
|
487
|
+
|
|
488
|
+
<table>
|
|
489
|
+
<tr><th>Tool</th><th>What it does</th><th>What it doesn't</th></tr>
|
|
490
|
+
<tr><td><strong>dotenvx</strong></td><td>Encrypts .env, safe to commit</td><td>Git merge conflicts, no CF Workers sync</td></tr>
|
|
491
|
+
<tr><td><strong>Infisical / Doppler</strong></td><td>Centralized secrets, CF Workers sync</td><td>SaaS dependency, overkill for small teams</td></tr>
|
|
492
|
+
<tr><td><strong>wrangler secret</strong></td><td>Sets CF Workers secrets</td><td>One key at a time, no bulk diff, no .env integration</td></tr>
|
|
493
|
+
<tr><td><strong>CF Secrets Store</strong></td><td>Account-level secrets</td><td>Broken local dev, no .env sync</td></tr>
|
|
494
|
+
<tr><td><strong>.dev.vars</strong></td><td>Local dev secrets</td><td>Doesn't sync with anything</td></tr>
|
|
495
|
+
</table>
|
|
496
|
+
|
|
497
|
+
**envsync** fills the gap: encrypted `.env` files as the single source of truth, synced to every target — Workers secrets, `.dev.vars`, validation — with monorepo and multi-environment support built in.
|
|
498
|
+
|
|
499
|
+
No SaaS. No dashboard. Just a CLI, your `.env` files, and Cloudflare's API.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Tech Stack
|
|
504
|
+
|
|
505
|
+
| | |
|
|
506
|
+
|---|---|
|
|
507
|
+
| **Runtime** | [Node.js](https://nodejs.org) >= 18 or [Bun](https://bun.sh) |
|
|
508
|
+
| **CLI framework** | [citty](https://github.com/unjs/citty) |
|
|
509
|
+
| **Output** | [consola](https://github.com/unjs/consola) |
|
|
510
|
+
| **Config loading** | [jiti](https://github.com/unjs/jiti) |
|
|
511
|
+
| **Encryption** | [@dotenvx/dotenvx](https://dotenvx.com) |
|
|
512
|
+
| **CF Secrets** | [wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI (shell out) |
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## License
|
|
517
|
+
|
|
518
|
+
MIT
|