Skip to content

Commit c946386

Browse files
authored
sync local env files with cloud on pull/push (#6)
* sync local env files with cloud on pull/push * refactor(env): simplify env sync module - Remove unused assetFileNames param from readProjectEnvFiles - Consolidate config DTO builders via shared helpers - Extract missing-env-file push warnings into envSync * refactor(env): extract pull/push env orchestration - Partition cloud config into asset vs non-asset env variables - Add prepareEnvPushState and computeEnvPullChanges helpers - Slim push and sync command env wiring * refactor(env): trim tests and dedupe env sync * fix(assets): archive removed assets on push Also drop stale asset env keys from appConfig so pull doesn’t resurrect deleted asset vars * feat(env): implement pruning of stale asset env entries on push * fix(push): confirm cloud env/secrets wipe Missing or empty .env.config/.env.secrets now warn and prompt [y/N] before clearing cloud values; empty secrets push {} * feat(env): alias-scoped env sync for push/pull - resolve base vs .env.*.<alias> by default alias + file presence - non-default pull creates scoped files; never stomps base - missing env file skips push side; empty file wipes with confirm - pull asset keys to resolved config path - wire push/pull/release + docs/tests * fix(release): honor --app on subcommands - Commander ate --app when parent+child both declared it - read --app from release parent opts in create/list/use - align Firestore version id with GCS snapshot path - alias-aware success/list hints for non-default apps * fix(release): honor --app, sync version ids
1 parent 982ffd8 commit c946386

18 files changed

Lines changed: 2344 additions & 160 deletions

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ ensemble update
119119
3. Run `ensemble push` to sync your local app (screens, widgets, scripts, etc.) with the cloud
120120
4. Optionally run `ensemble pull` to refresh local artifacts from the cloud when other collaborators change them
121121

122+
### Environment files
123+
124+
Config and secrets sync on `push`, `pull`, and `release use`:
125+
126+
- **Base**: `.env.config`, `.env.secrets` (shared defaults)
127+
- **Per-alias** (non-default alias, or when both scoped files exist): `.env.config.<alias>`, `.env.secrets.<alias>`
128+
- Default alias with only base files uses the base pair; other aliases never overwrite base on pull
129+
- **Missing** local file → that side skipped on push (no cloud wipe)
130+
- **Empty** local file → wipe cloud keys on that side (with confirmation)
131+
132+
Details: [docs/Env-config-aliases.md](docs/Env-config-aliases.md).
133+
122134
### Versions / releases (snapshots)
123135

124136
You can save and use snapshots of your app state in the cloud:

docs/Env-config-aliases.md

Lines changed: 36 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Environment config + secrets files (`.env.config` / `.env.secrets` + per-alias overrides)
22

3-
This document proposes an environment-variable architecture for the Ensemble CLI that supports **multiple app environments** (or “targets”) cleanly and safely.
3+
This document describes the environment-variable architecture used by the Ensemble CLI for **multiple app environments** (or “targets”).
44

55
The chosen approach is:
66

@@ -67,97 +67,68 @@ api_url=https://prod.ensemble.com
6767

6868
---
6969

70-
## Resolution / precedence rules
70+
## Resolution rules
7171

72-
When running a command for a given alias, apply the same rules to **config** and **secrets**:
72+
For the active alias (`--app` or `ensemble.config.json``default`):
7373

74-
- Config:
75-
1. Read `.env.config` if present (base defaults).
76-
2. Read `.env.config.<alias>` if present (alias overrides).
77-
3. Merge by key where **alias overrides win**.
78-
- Secrets:
79-
1. Read `.env.secrets` if present (base defaults).
80-
2. Read `.env.secrets.<alias>` if present (alias overrides).
81-
3. Merge by key where **alias overrides win**.
74+
| Situation | Files used |
75+
| ---------------------------------------------- | --------------------------------------------------------------------------- |
76+
| Alias is **default** and only base files exist | `.env.config` + `.env.secrets` |
77+
| Alias is **not default** | `.env.config.<alias>` + `.env.secrets.<alias>` (created on pull if missing) |
78+
| Alias has **both** scoped files (any alias) | scoped pair wins over base |
8279

83-
If only `.env.config` exists, behavior matches today (backwards compatible). Secrets files are additive and optional.
80+
No mixing across tiers. Config and secrets always come from the same tier.
8481

85-
### Why this precedence?
86-
87-
- Shared values (common across envs) live in one place.
88-
- Environment-specific values override without duplicating the whole file.
82+
Pulling a non-default alias (e.g. `ensemble pull --app uat`) writes cloud env into `.env.config.uat` / `.env.secrets.uat` and leaves base files untouched.
8983

9084
---
9185

92-
## CLI behavior (proposed)
93-
94-
### Reading env config
86+
## CLI behavior
9587

96-
Commands that need env config should use the **effective env config** for the selected `--app` alias.
88+
### Reading env files
9789

98-
- If `--app` is omitted, treat it as `default` (existing behavior).
99-
- If `.env.config.<alias>` is missing, fall back to `.env.config` only.
90+
Commands use the resolved pair for the selected `--app` alias (see resolution rules above).
10091

101-
### Pushing env variables
92+
`--app` is optional and defaults to `ensemble.config.json``default`.
10293

103-
If the CLI supports pushing env vars to the cloud, it should be **explicitly scoped**:
94+
### Missing vs empty (push)
10495

105-
- `ensemble push --app prod` may only push the **prod effective env config**.
106-
- It must never push dev values to prod unless the user explicitly made them prod values (via `.env.config.prod` or identical base defaults).
96+
| Local state | Push behavior |
97+
| ----------------------- | ----------------------------------------------------------------- |
98+
| File **missing** | Ignored — no env push for that side, no cloud wipe |
99+
| File **present, empty** | Wipe — warn + `[y/N]` before deleting all cloud keys on that side |
107100

108-
Recommended sync semantics:
101+
### Pushing env variables
109102

110-
- Default: **upsert/patch** (add/update keys present in local effective env config).
111-
- Optional: `--delete-missing` (dangerous) to remove remote keys not present locally.
112-
- Optional: `--dry-run` to show changes without applying.
103+
- `ensemble push --app <alias>` pushes the **effective** env for that alias.
104+
- Config and secrets are pushed independently (missing file → that side skipped).
113105

114106
### Pulling env variables
115107

116-
Similarly, pulling should be scoped:
108+
- `ensemble pull --app <alias>` writes cloud env into the scoped target file when in scoped mode (`.env.config.<alias>` / `.env.secrets.<alias>`), leaving the base file untouched.
109+
- In legacy mode, pull continues to write `.env.config` / `.env.secrets`.
117110

118-
- `ensemble pull --app prod` should update **only** `.env.config.prod` (or optionally print a diff).
119-
- Avoid writing prod keys into `.env.config` unless explicitly requested.
111+
### Release use
112+
113+
- `ensemble release use` restores snapshot config into the same write target as pull (scoped or base).
120114

121115
---
122116

123117
## Asset-generated keys and `.env.config`
124118

125-
Today, the CLI upserts `.env.config` to ensure asset-related keys exist after:
119+
The CLI upserts `.env.config` for asset-related keys after:
126120

127121
- `ensemble add asset`
128122
- `ensemble push` (asset upload)
129-
- `ensemble pull` (asset sync)
130-
131-
This design proposes:
132-
133-
- Keep `.env.config` as a base defaults file for users.
134-
- Write **asset-generated keys into the alias file** by default (because assets are associated with a specific app target).
135123

136-
Suggested split:
137-
138-
- `.env.config`: user-managed shared defaults (checked in or not—team choice)
139-
- `.env.config.<alias>`: app-target-specific values, including:
140-
- `assets=<baseUrl>` for that target
141-
- any cloud-provided asset usage env keys for that target
142-
143-
Backwards-compatibility note:
144-
145-
- If alias files are not in use yet, continue writing to `.env.config` as today.
146-
- Once alias files exist (or a new setting/flag opts into alias-mode), write to alias files.
124+
Pull writes asset env keys (`assets=`, per-asset keys) into the resolved config file for the active alias (base or scoped). `ensemble add asset` still upserts the base `.env.config`.
147125

148126
---
149127

150-
## Safety and production protections
151-
152-
To reduce “oops pushed dev to prod” failures:
128+
## Safety
153129

154-
- **Require explicit target** for sensitive operations (recommended UX):
155-
- For example, pushing env vars could require `--app` when multiple apps exist in `ensemble.config.json`.
156-
- **Stronger confirmations** for production-like aliases (e.g. `prod`, `production`):
157-
- Show a diff summary
158-
- Require a typed confirmation or `--yes`
159-
- **Never default to destructive deletes**:
160-
- `--delete-missing` must be opt-in.
130+
- **Never default to destructive deletes** except when a local env file exists but is empty (explicit wipe semantics above).
131+
- **`--delete-missing`** is not implemented; local-only keys are not auto-deleted from cloud on push.
161132

162133
---
163134

@@ -181,14 +152,6 @@ At minimum, consider adding these to `.gitignore`:
181152

182153
If you _do_ want to commit alias files for non-secret config, use a more selective ignore pattern or separate “public” vs “secret” configs.
183154

184-
### CLI handling expectations for secrets
185-
186-
If/when the CLI reads or syncs secrets:
187-
188-
- Never print secret values in logs (even in `--verbose`).
189-
- Prefer diff output that only shows keys changed (and counts), not values.
190-
- Consider stronger confirmations / restrictions for production aliases.
191-
192155
---
193156

194157
## Examples
@@ -233,16 +196,11 @@ assets=https://assets.prod.ensemble.com/
233196

234197
---
235198

236-
## Migration plan (incremental)
199+
## Migration plan
237200

238-
1. **Introduce alias file support** in read-paths:
239-
- merge base + alias override (alias wins)
240-
2. **Introduce alias-aware write-paths**:
241-
- write generated keys (assets) into `.env.config.<alias>` when applicable
242-
3. **Add env push/pull commands or flags** (if desired):
243-
- ensure all operations are scoped to `--app <alias>`
244-
4. **Add guardrails**:
245-
- diffs, confirmations for prod, optional delete-missing
201+
1. Single-app projects: no change — keep using `.env.config` / `.env.secrets`.
202+
2. Multi-app projects: add `.env.config.<alias>` / `.env.secrets.<alias>` for per-target overrides; shared defaults stay in the base files.
203+
3. Existing single-app repos can opt in early by creating a scoped file (e.g. `.env.config.dev`).
246204

247205
---
248206

0 commit comments

Comments
 (0)