cf-envsync 0.3.0 → 0.3.1
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 +132 -0
- package/dist/index.js +114 -33
- package/package.json +1 -1
- package/src/types/config.ts +5 -0
package/README.md
CHANGED
|
@@ -93,6 +93,135 @@ envsync validate
|
|
|
93
93
|
|
|
94
94
|
---
|
|
95
95
|
|
|
96
|
+
## 5-Minute Tutorial
|
|
97
|
+
|
|
98
|
+
A complete walkthrough: project setup → local dev → deploy to staging → validate.
|
|
99
|
+
|
|
100
|
+
### 1. Initialize your project
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# In your monorepo root
|
|
104
|
+
npm install -D cf-envsync
|
|
105
|
+
envsync init --monorepo
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This scans for `wrangler.jsonc` files, discovers your workers, and generates:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
envsync.config.ts ← config with all apps/workers detected
|
|
112
|
+
.env.example ← key reference (committed to git)
|
|
113
|
+
.env ← local shared secrets
|
|
114
|
+
.env.staging ← staging secrets (empty, you'll fill these)
|
|
115
|
+
.env.production ← production secrets (empty)
|
|
116
|
+
.gitignore ← updated with .env.local, .env.password, **/.dev.vars
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Fill in your secrets
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# .env — local development values (committed, encrypted)
|
|
123
|
+
DATABASE_URL=postgres://localhost:5432/mydb
|
|
124
|
+
JWT_SECRET=dev_jwt_secret
|
|
125
|
+
AUTH_SECRET=dev_auth_secret
|
|
126
|
+
API_URL=http://localhost:8787
|
|
127
|
+
|
|
128
|
+
# .env.staging — staging values
|
|
129
|
+
DATABASE_URL=postgres://staging-db.example.com/mydb
|
|
130
|
+
JWT_SECRET=staging_jwt_secret_abc
|
|
131
|
+
AUTH_SECRET=staging_auth_secret_xyz
|
|
132
|
+
API_URL=https://api-staging.example.com
|
|
133
|
+
|
|
134
|
+
# .env.local — YOUR dev-specific overrides (gitignored, each dev has their own)
|
|
135
|
+
OAUTH_REDIRECT_URL=https://my-tunnel.ngrok.io/callback
|
|
136
|
+
DEV_TUNNEL_URL=https://my-tunnel.ngrok.io
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Encrypt before committing (optional)
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Set a password
|
|
143
|
+
echo "ENVSYNC_PASSWORD=my-team-password" > .env.password
|
|
144
|
+
|
|
145
|
+
# Encrypt all plain values
|
|
146
|
+
envsync encrypt staging
|
|
147
|
+
envsync encrypt production
|
|
148
|
+
|
|
149
|
+
# Now .env.staging looks like:
|
|
150
|
+
# DATABASE_URL=envsync:v1:base64payload...
|
|
151
|
+
# JWT_SECRET=envsync:v1:base64payload...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4. Local development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
envsync dev
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This reads `.env` + `.env.local`, merges them, and writes `.dev.vars` into each app directory. Start wrangler as usual — it reads `.dev.vars` automatically.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
apps/api/.dev.vars ← DATABASE_URL, JWT_SECRET, API_URL, OAUTH_REDIRECT_URL
|
|
164
|
+
apps/web/.dev.vars ← AUTH_SECRET, VITE_API_URL, VITE_OAUTH_REDIRECT_URL
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> **Vite / non-wrangler apps:** `.dev.vars` is for wrangler only. If your app runs with `vite dev`, set `devFile` in your config:
|
|
168
|
+
>
|
|
169
|
+
> ```ts
|
|
170
|
+
> apps: {
|
|
171
|
+
> web: {
|
|
172
|
+
> path: "apps/web",
|
|
173
|
+
> devFile: ".env.local", // Vite reads this
|
|
174
|
+
> // or generate both:
|
|
175
|
+
> // devFile: [".dev.vars", ".env.local"],
|
|
176
|
+
> },
|
|
177
|
+
> }
|
|
178
|
+
> ```
|
|
179
|
+
|
|
180
|
+
If you forgot to set a per-dev override, envsync tells you:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
⚠ Missing in .env.local: DEV_TUNNEL_URL (required per-dev override)
|
|
184
|
+
→ echo "DEV_TUNNEL_URL=https://your-tunnel.example.com" >> .env.local
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 5. Push to staging
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Preview first
|
|
191
|
+
envsync push staging --dry-run
|
|
192
|
+
|
|
193
|
+
# Push for real
|
|
194
|
+
envsync push staging
|
|
195
|
+
# Push 4 secrets to worker "my-api-staging" (staging)? yes
|
|
196
|
+
# ✓ Pushed 4 secrets to my-api-staging
|
|
197
|
+
# Push 1 secrets to worker "my-web-staging" (staging)? yes
|
|
198
|
+
# ✓ Pushed 1 secrets to my-web-staging
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 6. Validate before deploying
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
envsync validate
|
|
205
|
+
# Checks every app × every environment against .env.example
|
|
206
|
+
# Exit code 1 if anything is missing → safe for CI
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 7. CI/CD integration
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
# GitHub Actions example
|
|
213
|
+
- name: Validate env vars
|
|
214
|
+
run: envsync validate
|
|
215
|
+
|
|
216
|
+
- name: Push secrets to production
|
|
217
|
+
run: envsync push production --force
|
|
218
|
+
env:
|
|
219
|
+
ENVSYNC_PASSWORD: ${{ secrets.ENVSYNC_PASSWORD }}
|
|
220
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
96
225
|
## Commands
|
|
97
226
|
|
|
98
227
|
### `envsync dev` — Generate `.dev.vars`
|
|
@@ -130,6 +259,8 @@ Done!
|
|
|
130
259
|
|
|
131
260
|
Every key shows exactly where its value came from. Missing per-dev overrides are caught immediately.
|
|
132
261
|
|
|
262
|
+
> **Vite / non-wrangler apps:** Set `devFile: ".env.local"` in your app config. See [the tutorial](#4-local-development).
|
|
263
|
+
|
|
133
264
|
---
|
|
134
265
|
|
|
135
266
|
### `envsync push` — Deploy secrets
|
|
@@ -459,6 +590,7 @@ export default {
|
|
|
459
590
|
| `apps.{name}.workers` | `Record<string, string>` | Worker name per environment |
|
|
460
591
|
| `apps.{name}.secrets` | `string[]` | Secret keys pushed via `wrangler secret bulk` |
|
|
461
592
|
| `apps.{name}.vars` | `string[]` | Non-secret env vars (not pushed as secrets) |
|
|
593
|
+
| `apps.{name}.devFile` | `string \| string[]` | Output file(s) for `envsync dev`. Default: `".dev.vars"`. Use `".env.local"` for Vite apps, or an array for both |
|
|
462
594
|
| `shared` | `string[]` | Keys with the same value across multiple apps |
|
|
463
595
|
| `local.overrides` | `string[]` | Keys each developer must set in `.env.local` |
|
|
464
596
|
| `local.perApp` | `Record<string, string[]>` | Per-app developer override keys |
|
package/dist/index.js
CHANGED
|
@@ -2315,11 +2315,13 @@ function resolveConfig(config, cwd) {
|
|
|
2315
2315
|
allKeys.push(key);
|
|
2316
2316
|
}
|
|
2317
2317
|
}
|
|
2318
|
+
const devFiles = app.devFile ? Array.isArray(app.devFile) ? app.devFile : [app.devFile] : [".dev.vars"];
|
|
2318
2319
|
apps[name] = {
|
|
2319
2320
|
...app,
|
|
2320
2321
|
name,
|
|
2321
2322
|
absolutePath: resolve2(projectRoot, app.path),
|
|
2322
|
-
allKeys
|
|
2323
|
+
allKeys,
|
|
2324
|
+
devFiles
|
|
2323
2325
|
};
|
|
2324
2326
|
}
|
|
2325
2327
|
return {
|
|
@@ -2333,15 +2335,23 @@ function resolveApps(config, appNames) {
|
|
|
2333
2335
|
if (!appNames || appNames.length === 0) {
|
|
2334
2336
|
return Object.values(config.apps);
|
|
2335
2337
|
}
|
|
2338
|
+
const available = Object.keys(config.apps);
|
|
2336
2339
|
const resolved = [];
|
|
2340
|
+
const unknown = [];
|
|
2337
2341
|
for (const name of appNames) {
|
|
2338
2342
|
const app = config.apps[name];
|
|
2339
2343
|
if (!app) {
|
|
2340
|
-
|
|
2344
|
+
unknown.push(name);
|
|
2341
2345
|
continue;
|
|
2342
2346
|
}
|
|
2343
2347
|
resolved.push(app);
|
|
2344
2348
|
}
|
|
2349
|
+
if (unknown.length > 0) {
|
|
2350
|
+
for (const name of unknown) {
|
|
2351
|
+
consola.error(`Unknown app: "${name}". Available: ${available.join(", ")}`);
|
|
2352
|
+
}
|
|
2353
|
+
process.exit(1);
|
|
2354
|
+
}
|
|
2345
2355
|
return resolved;
|
|
2346
2356
|
}
|
|
2347
2357
|
function getWorkerName(app, environment) {
|
|
@@ -13166,6 +13176,16 @@ async function loadEnvFile(filePath, env2, projectRoot, encryption) {
|
|
|
13166
13176
|
return {};
|
|
13167
13177
|
}
|
|
13168
13178
|
const content = await readFile(filePath);
|
|
13179
|
+
if (encryption) {
|
|
13180
|
+
const hasEnvsyncValues = content.includes("envsync:v1:");
|
|
13181
|
+
const hasDotenvxValues = content.includes("encrypted:");
|
|
13182
|
+
if (encryption === "dotenvx" && hasEnvsyncValues) {
|
|
13183
|
+
consola.warn(`${filePath}: config uses encryption: "dotenvx" but file contains "envsync:v1:" (password-encrypted) values. ` + `Check your encryption setting.`);
|
|
13184
|
+
}
|
|
13185
|
+
if (encryption === "password" && hasDotenvxValues && !hasEnvsyncValues) {
|
|
13186
|
+
consola.warn(`${filePath}: config uses encryption: "password" but file contains dotenvx-encrypted values. ` + `Check your encryption setting.`);
|
|
13187
|
+
}
|
|
13188
|
+
}
|
|
13169
13189
|
if (encryption === "password") {
|
|
13170
13190
|
const envMap = parsePlainEnv(content);
|
|
13171
13191
|
const password = findPassword(env2, projectRoot);
|
|
@@ -13228,17 +13248,22 @@ function getLocalOverridePath(config) {
|
|
|
13228
13248
|
return join3(config.projectRoot, config.raw.envFiles.local);
|
|
13229
13249
|
}
|
|
13230
13250
|
var init_env_file = __esm(() => {
|
|
13251
|
+
init_dist2();
|
|
13231
13252
|
init_encryption();
|
|
13232
13253
|
init_config();
|
|
13233
13254
|
init_fs();
|
|
13234
13255
|
});
|
|
13235
13256
|
|
|
13236
13257
|
// src/core/resolver.ts
|
|
13237
|
-
import { normalize } from "node:path";
|
|
13258
|
+
import { normalize, relative } from "node:path";
|
|
13238
13259
|
async function resolveAppEnv(config, app, environment) {
|
|
13239
13260
|
const layers = [];
|
|
13240
13261
|
const encryption = config.raw.encryption;
|
|
13241
13262
|
const rootEnvPath = getRootEnvPath(config, environment);
|
|
13263
|
+
if (!fileExists(rootEnvPath) && environment !== "local" && !warnedMissingFiles.has(rootEnvPath)) {
|
|
13264
|
+
warnedMissingFiles.add(rootEnvPath);
|
|
13265
|
+
consola.warn(`Missing env file: ${relative(config.projectRoot, rootEnvPath)} (create it or run \`envsync init\`)`);
|
|
13266
|
+
}
|
|
13242
13267
|
const rootEnv = await loadEnvFile(rootEnvPath, environment, config.projectRoot, encryption);
|
|
13243
13268
|
if (Object.keys(rootEnv).length > 0) {
|
|
13244
13269
|
layers.push({ source: rootEnvPath, map: rootEnv });
|
|
@@ -13281,8 +13306,12 @@ function findMissingOverrides(config, app, localEnv) {
|
|
|
13281
13306
|
}
|
|
13282
13307
|
return missing;
|
|
13283
13308
|
}
|
|
13309
|
+
var warnedMissingFiles;
|
|
13284
13310
|
var init_resolver = __esm(() => {
|
|
13311
|
+
init_dist2();
|
|
13285
13312
|
init_env_file();
|
|
13313
|
+
init_fs();
|
|
13314
|
+
warnedMissingFiles = new Set;
|
|
13286
13315
|
});
|
|
13287
13316
|
|
|
13288
13317
|
// src/commands/dev.ts
|
|
@@ -13290,13 +13319,13 @@ var exports_dev = {};
|
|
|
13290
13319
|
__export(exports_dev, {
|
|
13291
13320
|
default: () => dev_default
|
|
13292
13321
|
});
|
|
13293
|
-
import { join as join4, relative } from "node:path";
|
|
13322
|
+
import { join as join4, relative as relative2 } from "node:path";
|
|
13294
13323
|
function parseAppNames(args) {
|
|
13295
13324
|
const rest = args._;
|
|
13296
13325
|
return rest?.length ? rest : undefined;
|
|
13297
13326
|
}
|
|
13298
13327
|
function formatSource(source, key, projectRoot, sharedKeys, localOverrideKeys) {
|
|
13299
|
-
const rel =
|
|
13328
|
+
const rel = relative2(projectRoot, source);
|
|
13300
13329
|
if (localOverrideKeys.has(key))
|
|
13301
13330
|
return `${rel} (per-dev override)`;
|
|
13302
13331
|
if (sharedKeys.has(key))
|
|
@@ -13352,26 +13381,21 @@ var init_dev = __esm(() => {
|
|
|
13352
13381
|
const localEnv = await loadEnvFile(localOverridePath);
|
|
13353
13382
|
for (const app of apps) {
|
|
13354
13383
|
const resolved = await resolveAppEnv(config, app, environment);
|
|
13355
|
-
const devVarsPath = join4(app.absolutePath, ".dev.vars");
|
|
13356
|
-
const relDevVars = relative(config.projectRoot, devVarsPath);
|
|
13357
13384
|
if (Object.keys(resolved.map).length === 0) {
|
|
13358
13385
|
consola.warn(` No env vars resolved for ${app.name}. Skipping.`);
|
|
13359
13386
|
continue;
|
|
13360
13387
|
}
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
consola.log(`
|
|
13388
|
+
for (const devFileName of app.devFiles) {
|
|
13389
|
+
const devFilePath = join4(app.absolutePath, devFileName);
|
|
13390
|
+
const relDevFile = relative2(config.projectRoot, devFilePath);
|
|
13391
|
+
if (args["dry-run"]) {
|
|
13392
|
+
consola.log(`
|
|
13393
|
+
${relDevFile}`);
|
|
13394
|
+
} else {
|
|
13395
|
+
await writeEnvFile(devFilePath, resolved.map);
|
|
13396
|
+
consola.log(`
|
|
13397
|
+
${relDevFile}`);
|
|
13370
13398
|
}
|
|
13371
|
-
} else {
|
|
13372
|
-
await writeEnvFile(devVarsPath, resolved.map);
|
|
13373
|
-
consola.log(`
|
|
13374
|
-
${relDevVars}`);
|
|
13375
13399
|
for (let i2 = 0;i2 < resolved.entries.length; i2++) {
|
|
13376
13400
|
const entry = resolved.entries[i2];
|
|
13377
13401
|
const isLast = i2 === resolved.entries.length - 1;
|
|
@@ -13385,12 +13409,18 @@ var init_dev = __esm(() => {
|
|
|
13385
13409
|
if (missing.length > 0) {
|
|
13386
13410
|
for (const key of missing) {
|
|
13387
13411
|
consola.warn(`
|
|
13388
|
-
⚠ Missing in ${
|
|
13389
|
-
consola.log(` → echo "${key}=<your-value>" >> ${
|
|
13412
|
+
⚠ Missing in ${relative2(config.projectRoot, localOverridePath)}: ${key} (required per-dev override)`);
|
|
13413
|
+
consola.log(` → echo "${key}=<your-value>" >> ${relative2(config.projectRoot, localOverridePath)}`);
|
|
13390
13414
|
}
|
|
13391
13415
|
}
|
|
13392
13416
|
}
|
|
13393
13417
|
}
|
|
13418
|
+
if (environment !== "local") {
|
|
13419
|
+
const hasOverrides = (config.raw.local?.overrides?.length ?? 0) > 0 || Object.keys(config.raw.local?.perApp ?? {}).length > 0;
|
|
13420
|
+
if (hasOverrides) {
|
|
13421
|
+
consola.info(`Per-dev overrides are only applied in "local" environment (current: ${environment}).`);
|
|
13422
|
+
}
|
|
13423
|
+
}
|
|
13394
13424
|
consola.success(`
|
|
13395
13425
|
Done!`);
|
|
13396
13426
|
}
|
|
@@ -13572,14 +13602,33 @@ var init_push = __esm(() => {
|
|
|
13572
13602
|
consola.warn("No apps to process.");
|
|
13573
13603
|
return;
|
|
13574
13604
|
}
|
|
13605
|
+
if (args.force && !args["dry-run"]) {
|
|
13606
|
+
const targets = apps.map((app) => {
|
|
13607
|
+
const w2 = getWorkerName(app, environment);
|
|
13608
|
+
return w2 ? `${app.name} → ${w2}` : null;
|
|
13609
|
+
}).filter(Boolean);
|
|
13610
|
+
if (targets.length > 0) {
|
|
13611
|
+
consola.warn(`Force-pushing to ${environment}: ${targets.join(", ")}`);
|
|
13612
|
+
}
|
|
13613
|
+
}
|
|
13575
13614
|
const sharedKeys = new Set(config.raw.shared ?? []);
|
|
13576
|
-
|
|
13615
|
+
let hasFailure = false;
|
|
13616
|
+
if (args.shared) {
|
|
13617
|
+
if (sharedKeys.size === 0) {
|
|
13618
|
+
consola.error("No shared keys defined in config. Nothing to push with --shared.");
|
|
13619
|
+
process.exit(1);
|
|
13620
|
+
}
|
|
13621
|
+
consola.info(`--shared: pushing only shared keys (${[...sharedKeys].join(", ")})`);
|
|
13622
|
+
}
|
|
13623
|
+
for (let i2 = 0;i2 < apps.length; i2++) {
|
|
13624
|
+
const app = apps[i2];
|
|
13625
|
+
const progress = apps.length > 1 ? `[${i2 + 1}/${apps.length}] ` : "";
|
|
13577
13626
|
const workerName = getWorkerName(app, environment);
|
|
13578
13627
|
if (!workerName) {
|
|
13579
|
-
consola.warn(
|
|
13628
|
+
consola.warn(`${progress}No worker defined for ${app.name} in ${environment}. Skipping.`);
|
|
13580
13629
|
continue;
|
|
13581
13630
|
}
|
|
13582
|
-
consola.start(
|
|
13631
|
+
consola.start(`${progress}Pushing secrets for ${app.name} → ${workerName} (${environment})...`);
|
|
13583
13632
|
const resolved = await resolveAppEnv(config, app, environment);
|
|
13584
13633
|
let secretsToPush;
|
|
13585
13634
|
if (args.shared) {
|
|
@@ -13600,7 +13649,8 @@ var init_push = __esm(() => {
|
|
|
13600
13649
|
}
|
|
13601
13650
|
const keyCount = Object.keys(secretsToPush).length;
|
|
13602
13651
|
if (keyCount === 0) {
|
|
13603
|
-
|
|
13652
|
+
const reason = args.shared ? " (no shared keys for this app)" : "";
|
|
13653
|
+
consola.warn(` No secrets to push for ${app.name}${reason}. Skipping.`);
|
|
13604
13654
|
continue;
|
|
13605
13655
|
}
|
|
13606
13656
|
if (args["dry-run"]) {
|
|
@@ -13623,8 +13673,13 @@ var init_push = __esm(() => {
|
|
|
13623
13673
|
consola.success(` Pushed ${keyCount} secrets to ${workerName}`);
|
|
13624
13674
|
} else {
|
|
13625
13675
|
consola.error(` Failed to push secrets to ${workerName}`);
|
|
13676
|
+
hasFailure = true;
|
|
13626
13677
|
}
|
|
13627
13678
|
}
|
|
13679
|
+
if (hasFailure) {
|
|
13680
|
+
consola.error("Some pushes failed.");
|
|
13681
|
+
process.exit(1);
|
|
13682
|
+
}
|
|
13628
13683
|
consola.success("Done!");
|
|
13629
13684
|
}
|
|
13630
13685
|
});
|
|
@@ -13760,11 +13815,18 @@ var init_validate = __esm(() => {
|
|
|
13760
13815
|
let environments;
|
|
13761
13816
|
let appNames;
|
|
13762
13817
|
if (envArg && config.environments.includes(envArg)) {
|
|
13818
|
+
if (envArg in config.apps) {
|
|
13819
|
+
consola.error(`"${envArg}" is both an environment and an app name. This is ambiguous.`);
|
|
13820
|
+
consola.info(` To validate environment: envsync validate ${envArg} --
|
|
13821
|
+
` + ` To validate app: envsync validate -- ${envArg}`);
|
|
13822
|
+
process.exit(1);
|
|
13823
|
+
}
|
|
13763
13824
|
environments = [envArg];
|
|
13764
13825
|
appNames = parseAppNames4(args);
|
|
13765
13826
|
} else if (envArg) {
|
|
13766
13827
|
environments = config.environments;
|
|
13767
13828
|
appNames = parseAppNames4(args, 0);
|
|
13829
|
+
consola.info(`"${envArg}" is not an environment. Treating as app name. Validating all environments.`);
|
|
13768
13830
|
} else {
|
|
13769
13831
|
environments = config.environments;
|
|
13770
13832
|
appNames = undefined;
|
|
@@ -13783,10 +13845,12 @@ var init_validate = __esm(() => {
|
|
|
13783
13845
|
...config.raw.local?.overrides ?? [],
|
|
13784
13846
|
...Object.values(config.raw.local?.perApp ?? {}).flat()
|
|
13785
13847
|
]);
|
|
13848
|
+
const totalChecks = environments.length * apps.length;
|
|
13786
13849
|
consola.log(`
|
|
13787
|
-
Checking against .env.example
|
|
13850
|
+
Checking against .env.example... (${environments.length} env × ${apps.length} app${apps.length > 1 ? "s" : ""})`);
|
|
13788
13851
|
const results = [];
|
|
13789
13852
|
let hasIssues = false;
|
|
13853
|
+
let checkIdx = 0;
|
|
13790
13854
|
for (const environment of environments) {
|
|
13791
13855
|
consola.log(`
|
|
13792
13856
|
${environment}`);
|
|
@@ -13951,8 +14015,15 @@ var init_diff = __esm(() => {
|
|
|
13951
14015
|
process.exit(1);
|
|
13952
14016
|
}
|
|
13953
14017
|
const target = args.target;
|
|
13954
|
-
const
|
|
13955
|
-
|
|
14018
|
+
const isEnv = target && config.environments.includes(target);
|
|
14019
|
+
const isApp = target && target in config.apps;
|
|
14020
|
+
if (isEnv && isApp) {
|
|
14021
|
+
consola.error(`"${target}" is both an environment and an app name. This is ambiguous.`);
|
|
14022
|
+
consola.info(` To compare environments: envsync diff ${env1} ${target} --
|
|
14023
|
+
` + ` To diff local vs remote: envsync diff ${env1} -- ${target}`);
|
|
14024
|
+
process.exit(1);
|
|
14025
|
+
}
|
|
14026
|
+
if (isEnv) {
|
|
13956
14027
|
const env2 = target;
|
|
13957
14028
|
const appNames = parseAppNames5(args, 2);
|
|
13958
14029
|
const apps = resolveApps(config, appNames);
|
|
@@ -14049,7 +14120,7 @@ var exports_init = {};
|
|
|
14049
14120
|
__export(exports_init, {
|
|
14050
14121
|
default: () => init_default
|
|
14051
14122
|
});
|
|
14052
|
-
import { join as join6, relative as
|
|
14123
|
+
import { join as join6, relative as relative3, basename } from "node:path";
|
|
14053
14124
|
function generateConfigTS(config) {
|
|
14054
14125
|
const lines = [
|
|
14055
14126
|
`import { defineConfig } from "cf-envsync";`,
|
|
@@ -14133,7 +14204,16 @@ var init_init = __esm(() => {
|
|
|
14133
14204
|
const existingConfig = CONFIG_FILES.find((f3) => fileExists(join6(cwd, f3)));
|
|
14134
14205
|
if (existingConfig) {
|
|
14135
14206
|
consola.warn(`${existingConfig} already exists.`);
|
|
14136
|
-
const
|
|
14207
|
+
const existingContent = await readFile(join6(cwd, existingConfig));
|
|
14208
|
+
const lines = existingContent.split(`
|
|
14209
|
+
`);
|
|
14210
|
+
const preview = lines.length > 20 ? [...lines.slice(0, 20), ` ... (${lines.length - 20} more lines)`].join(`
|
|
14211
|
+
`) : existingContent;
|
|
14212
|
+
consola.log(`
|
|
14213
|
+
Current config:
|
|
14214
|
+
${preview}
|
|
14215
|
+
`);
|
|
14216
|
+
const overwrite = await consola.prompt("Overwrite with new config?", {
|
|
14137
14217
|
type: "confirm"
|
|
14138
14218
|
});
|
|
14139
14219
|
if (!overwrite) {
|
|
@@ -14160,12 +14240,13 @@ var init_init = __esm(() => {
|
|
|
14160
14240
|
return (name === "wrangler.json" || name === "wrangler.jsonc") && !f3.includes("node_modules");
|
|
14161
14241
|
});
|
|
14162
14242
|
if (wranglerFiles.length === 0) {
|
|
14163
|
-
consola.warn(
|
|
14243
|
+
consola.warn(`No wrangler.json or wrangler.jsonc files found (searched all subdirectories, excluding node_modules).
|
|
14244
|
+
` + " Falling back to manual configuration.");
|
|
14164
14245
|
}
|
|
14165
14246
|
for (const wranglerFile of wranglerFiles.sort()) {
|
|
14166
14247
|
const fullPath = join6(cwd, wranglerFile);
|
|
14167
14248
|
const appDir = join6(cwd, wranglerFile, "..");
|
|
14168
|
-
const appPath =
|
|
14249
|
+
const appPath = relative3(cwd, appDir);
|
|
14169
14250
|
const appName = basename(appDir);
|
|
14170
14251
|
consola.info(` Found ${wranglerFile}`);
|
|
14171
14252
|
let wranglerConfig = {};
|
package/package.json
CHANGED
package/src/types/config.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface AppConfig {
|
|
|
41
41
|
secrets?: string[];
|
|
42
42
|
/** Var keys for this app (non-secret env vars) */
|
|
43
43
|
vars?: string[];
|
|
44
|
+
/** Output file(s) for `envsync dev`. Defaults to ".dev.vars".
|
|
45
|
+
* Use an array to generate multiple files, e.g. [".dev.vars", ".env.local"] */
|
|
46
|
+
devFile?: string | string[];
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
/** Resolved config after defaults and path resolution */
|
|
@@ -62,4 +65,6 @@ export interface ResolvedAppConfig extends AppConfig {
|
|
|
62
65
|
absolutePath: string;
|
|
63
66
|
/** All keys this app needs (secrets + vars + local overrides) */
|
|
64
67
|
allKeys: string[];
|
|
68
|
+
/** Normalized output file names for `envsync dev` (always an array) */
|
|
69
|
+
devFiles: string[];
|
|
65
70
|
}
|