envctl 2.3.1__tar.gz → 2.3.3__tar.gz
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.
- envctl-2.3.3/PKG-INFO +397 -0
- envctl-2.3.3/README.md +358 -0
- {envctl-2.3.1 → envctl-2.3.3}/pyproject.toml +8 -7
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/dotenv.py +32 -4
- envctl-2.3.3/src/envctl/adapters/input.py +47 -0
- envctl-2.3.3/src/envctl/adapters/process_environment.py +17 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/app.py +7 -2
- envctl-2.3.3/src/envctl/cli/callbacks.py +14 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/add/command.py +33 -42
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/check/command.py +22 -8
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/config/app.py +2 -3
- envctl-2.3.3/src/envctl/cli/commands/doctor/command.py +37 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/explain/command.py +14 -9
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/export/command.py +2 -4
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/fill/command.py +13 -18
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/init/command.py +12 -15
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/inspect/command.py +10 -8
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/copy.py +8 -7
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/create.py +6 -8
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/list.py +5 -3
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/path.py +6 -3
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/remove.py +12 -12
- envctl-2.3.3/src/envctl/cli/commands/project/commands/bind.py +30 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/rebind.py +17 -14
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/repair.py +10 -14
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/unbind.py +6 -9
- envctl-2.3.3/src/envctl/cli/commands/remove/command.py +53 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/set/command.py +6 -4
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/status/command.py +5 -4
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/sync/command.py +2 -5
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/unset/command.py +7 -8
- envctl-2.3.3/src/envctl/cli/commands/vault/commands/check.py +29 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/edit.py +6 -8
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/path.py +6 -3
- envctl-2.3.3/src/envctl/cli/commands/vault/commands/prune.py +69 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/show.py +30 -15
- envctl-2.3.3/src/envctl/cli/presenters/__init__.py +89 -0
- envctl-2.3.3/src/envctl/cli/presenters/action_presenter.py +242 -0
- envctl-2.3.3/src/envctl/cli/presenters/common.py +69 -0
- envctl-2.3.3/src/envctl/cli/presenters/doctor_presenter.py +59 -0
- envctl-2.3.3/src/envctl/cli/presenters/profile_presenter.py +58 -0
- envctl-2.3.3/src/envctl/cli/presenters/project_presenter.py +100 -0
- envctl-2.3.3/src/envctl/cli/presenters/resolution_presenter.py +84 -0
- envctl-2.3.3/src/envctl/cli/presenters/status_presenter.py +88 -0
- envctl-2.3.3/src/envctl/cli/presenters/vault_presenter.py +126 -0
- envctl-2.3.3/src/envctl/cli/prompts/__init__.py +17 -0
- envctl-2.3.3/src/envctl/cli/prompts/confirmation_prompts.py +45 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/serializers.py +12 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/loader.py +3 -24
- envctl-2.3.3/src/envctl/config/profile_resolution.py +54 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/contract.py +15 -1
- envctl-2.3.3/src/envctl/domain/expansion.py +101 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/operations.py +4 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/resolution.py +6 -0
- envctl-2.3.3/src/envctl/repository/profile_repository.py +140 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/add_service.py +38 -16
- envctl-2.3.3/src/envctl/services/doctor_service.py +161 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/fill_service.py +13 -13
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/profile_service.py +14 -40
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/rebind_service.py +6 -8
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/remove_service.py +35 -32
- envctl-2.3.3/src/envctl/services/resolution_service.py +506 -0
- envctl-2.3.3/src/envctl/services/set_service.py +35 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/status_service.py +6 -2
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/sync_service.py +4 -3
- envctl-2.3.3/src/envctl/services/unset_service.py +35 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/vault_service.py +51 -31
- envctl-2.3.3/src/envctl/utils/output.py +86 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_paths.py +10 -0
- envctl-2.3.3/src/envctl.egg-info/PKG-INFO +397 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/SOURCES.txt +16 -1
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/requires.txt +2 -1
- envctl-2.3.1/PKG-INFO +0 -302
- envctl-2.3.1/README.md +0 -264
- envctl-2.3.1/src/envctl/cli/callbacks.py +0 -33
- envctl-2.3.1/src/envctl/cli/commands/doctor/command.py +0 -55
- envctl-2.3.1/src/envctl/cli/commands/project/commands/bind.py +0 -31
- envctl-2.3.1/src/envctl/cli/commands/remove/command.py +0 -65
- envctl-2.3.1/src/envctl/cli/commands/vault/commands/check.py +0 -43
- envctl-2.3.1/src/envctl/cli/commands/vault/commands/prune.py +0 -58
- envctl-2.3.1/src/envctl/cli/formatters.py +0 -78
- envctl-2.3.1/src/envctl/services/doctor_service.py +0 -177
- envctl-2.3.1/src/envctl/services/resolution_service.py +0 -146
- envctl-2.3.1/src/envctl/services/set_service.py +0 -35
- envctl-2.3.1/src/envctl/services/unset_service.py +0 -35
- envctl-2.3.1/src/envctl/utils/output.py +0 -25
- envctl-2.3.1/src/envctl.egg-info/PKG-INFO +0 -302
- {envctl-2.3.1 → envctl-2.3.3}/LICENSE +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/setup.cfg +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/__main__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/editor.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/git.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/add/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/check/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/config/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/doctor/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/explain/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/export/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/fill/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/init/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/inspect/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/app.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/app.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/remove/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/run/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/run/command.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/set/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/status/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/sync/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/unset/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/app.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/decorators.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/runtime.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/defaults.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/writer.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/constants.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/app_config.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/contract_inference.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/doctor.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/project.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/runtime.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/status.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/errors.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/contract_repository.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/project_context.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/state_repository.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/bind_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/check_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/config_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/context_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/explain_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/export_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/init_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/inspect_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/repair_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/run_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/unbind_service.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/__init__.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/atomic.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/filesystem.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/masking.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_ids.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_names.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/shells.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/tilde.py +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/dependency_links.txt +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/entry_points.txt +0 -0
- {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/top_level.txt +0 -0
envctl-2.3.3/PKG-INFO
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envctl
|
|
3
|
+
Version: 2.3.3
|
|
4
|
+
Summary: Local environment control plane for contract-driven development workflows
|
|
5
|
+
Author-email: Alessandro Barbagallo <alessbarb@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/labrynx/envctl
|
|
8
|
+
Project-URL: Repository, https://github.com/labrynx/envctl
|
|
9
|
+
Project-URL: Issues, https://github.com/labrynx/envctl/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/labrynx/envctl/releases
|
|
11
|
+
Keywords: env,environment,dotenv,cli,developer-tools,configuration,secrets,contract-first
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
27
|
+
Requires-Dist: PyYAML<7.0,>=6.0
|
|
28
|
+
Requires-Dist: pydantic<3.0,>=2.7
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov<8.0.0,>=7.1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff<0.12,>=0.11; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy<2.0,>=1.10; extra == "dev"
|
|
34
|
+
Requires-Dist: types-PyYAML<7.0.0,>=6.0.12; extra == "dev"
|
|
35
|
+
Requires-Dist: build<2.0,>=1.2; extra == "dev"
|
|
36
|
+
Requires-Dist: twine<7.0,>=6.2; extra == "dev"
|
|
37
|
+
Requires-Dist: pkginfo<2.0,>=1.12; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# envctl
|
|
41
|
+
|
|
42
|
+
**Your `.env.local` files are undocumented, unvalidated, and drift between machines. envctl fixes that.**
|
|
43
|
+
|
|
44
|
+
[](https://github.com/labrynx/envctl/actions/workflows/ci.yml)
|
|
45
|
+
[](https://www.python.org/downloads/)
|
|
46
|
+
[](https://github.com/labrynx/envctl/blob/main/LICENSE)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## What is this?
|
|
51
|
+
|
|
52
|
+
Most projects handle `.env` files like this:
|
|
53
|
+
|
|
54
|
+
* variables are not documented
|
|
55
|
+
* values get copied between machines
|
|
56
|
+
* something works locally… but breaks somewhere else
|
|
57
|
+
|
|
58
|
+
`envctl` gives you a simple structure to fix that.
|
|
59
|
+
|
|
60
|
+
It separates three things that usually get mixed together:
|
|
61
|
+
|
|
62
|
+
* **what the project needs** → defined in `.envctl.schema.yaml` (committed to the repo)
|
|
63
|
+
* **what you have locally** → stored in a private vault (never in git)
|
|
64
|
+
* **what actually runs** → a validated environment, built on demand
|
|
65
|
+
|
|
66
|
+
So you get:
|
|
67
|
+
|
|
68
|
+
* no secrets in git
|
|
69
|
+
* no undocumented variables
|
|
70
|
+
* no copy-pasting `.env` files
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install envctl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or from source:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/labrynx/envctl
|
|
84
|
+
cd envctl
|
|
85
|
+
pip install -e .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Quickstart
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
envctl config init # create your local config
|
|
94
|
+
envctl init # initialize this repository
|
|
95
|
+
envctl fill # set missing values (interactive)
|
|
96
|
+
envctl check # validate against the contract
|
|
97
|
+
envctl run -- python app.py # run with env injected
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Why not just `.env.local`?
|
|
103
|
+
|
|
104
|
+
Because it doesn’t scale well.
|
|
105
|
+
|
|
106
|
+
| | `.env.local` | direnv | Doppler / Infisical | **envctl** |
|
|
107
|
+
| ------------------------------ | --------------- | ------------ | ------------------- | -------------------------- |
|
|
108
|
+
| Documents what variables exist | ❌ | ❌ | Partial | ✅ contract |
|
|
109
|
+
| Type validation | ❌ | ❌ | ❌ | ✅ |
|
|
110
|
+
| Values stay off git | ⚠️ easy to slip | ✅ | ✅ cloud | ✅ local vault |
|
|
111
|
+
| Multiple environments | manual files | manual files | ✅ | ✅ profiles |
|
|
112
|
+
| No cloud account required | ✅ | ✅ | ❌ | ✅ |
|
|
113
|
+
| Works in CI without mutation | ❌ | ❌ | ❌ | ✅ `ENVCTL_RUNTIME_MODE=ci` |
|
|
114
|
+
|
|
115
|
+
`envctl` is not a secrets manager.
|
|
116
|
+
|
|
117
|
+
It’s a **local control plane** for your project’s environment:
|
|
118
|
+
|
|
119
|
+
> the contract says what’s needed, your machine provides values, and envctl makes them work together.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## How it works
|
|
124
|
+
|
|
125
|
+
There are five pieces, but the idea is simple:
|
|
126
|
+
|
|
127
|
+
* **contract** → defines what variables exist and their rules
|
|
128
|
+
* **vault** → stores your real values locally
|
|
129
|
+
* **profile** → selects a set of values (`local`, `dev`, `staging`, …)
|
|
130
|
+
* **resolution** → combines everything in a deterministic way
|
|
131
|
+
* **projection** → makes it usable (`run`, `sync`, `export`)
|
|
132
|
+
|
|
133
|
+
Think of it like this:
|
|
134
|
+
|
|
135
|
+
> the repo defines the rules, your machine provides the data, and envctl builds the final environment.
|
|
136
|
+
|
|
137
|
+
Resolution now includes placeholder expansion as part of the runtime model, so `check`, `inspect`,
|
|
138
|
+
`run`, `sync`, and `export` all see the same final value.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Example contract
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
# .envctl.schema.yaml — commit this
|
|
146
|
+
version: 1
|
|
147
|
+
variables:
|
|
148
|
+
DATABASE_URL:
|
|
149
|
+
type: url
|
|
150
|
+
required: true
|
|
151
|
+
sensitive: true
|
|
152
|
+
description: Primary database connection URL
|
|
153
|
+
PORT:
|
|
154
|
+
type: int
|
|
155
|
+
required: true
|
|
156
|
+
default: 3000
|
|
157
|
+
sensitive: false
|
|
158
|
+
DEBUG:
|
|
159
|
+
type: bool
|
|
160
|
+
required: false
|
|
161
|
+
default: false
|
|
162
|
+
sensitive: false
|
|
163
|
+
TEST_JSON:
|
|
164
|
+
type: string
|
|
165
|
+
format: json
|
|
166
|
+
required: false
|
|
167
|
+
sensitive: false
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This file describes what exists.
|
|
171
|
+
It never contains real values.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Variable expansion
|
|
176
|
+
|
|
177
|
+
`envctl` supports explicit placeholder expansion with `${VAR}` during resolution.
|
|
178
|
+
|
|
179
|
+
That means the expansion happens before projection, so the effective expanded value is what:
|
|
180
|
+
|
|
181
|
+
* `inspect` shows
|
|
182
|
+
* `check` validates
|
|
183
|
+
* `run` injects
|
|
184
|
+
* `sync` writes
|
|
185
|
+
* `export` prints
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
|
|
189
|
+
```dotenv
|
|
190
|
+
INFRA_NEO4J_USER=neo4j
|
|
191
|
+
INFRA_NEO4J_PASSWORD=super-secret
|
|
192
|
+
INFRA_NEO4J_AUTH=${INFRA_NEO4J_USER}/${INFRA_NEO4J_PASSWORD}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`INFRA_NEO4J_AUTH` resolves to the final runtime value, not the literal expression.
|
|
196
|
+
|
|
197
|
+
Rules:
|
|
198
|
+
|
|
199
|
+
* only `${VAR}` is supported in v1
|
|
200
|
+
* `$VAR` stays literal
|
|
201
|
+
* if `VAR` is a declared envctl key, envctl resolves that key first
|
|
202
|
+
* otherwise envctl falls back to the current process environment
|
|
203
|
+
* `${HOME}` works when `HOME` exists in the current process environment
|
|
204
|
+
* malformed placeholders or unresolved references make resolution invalid
|
|
205
|
+
|
|
206
|
+
Compatibility notes:
|
|
207
|
+
|
|
208
|
+
* before this feature, `${HOME}` stayed literal
|
|
209
|
+
* now `${HOME}` is expanded during resolution
|
|
210
|
+
* `${...}` literal escaping is not supported in v1
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Profiles
|
|
215
|
+
|
|
216
|
+
Instead of juggling multiple `.env` files:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# set up dev once
|
|
220
|
+
envctl --profile dev fill
|
|
221
|
+
|
|
222
|
+
# validate staging
|
|
223
|
+
envctl --profile staging check
|
|
224
|
+
|
|
225
|
+
# run with staging values
|
|
226
|
+
envctl --profile staging run -- python app.py
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Profile selection priority:
|
|
230
|
+
|
|
231
|
+
1. `--profile`
|
|
232
|
+
2. `ENVCTL_PROFILE`
|
|
233
|
+
3. config default
|
|
234
|
+
4. `local`
|
|
235
|
+
|
|
236
|
+
Each profile is independent. No hidden inheritance.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Team workflow
|
|
241
|
+
|
|
242
|
+
The idea is simple:
|
|
243
|
+
|
|
244
|
+
* the **contract is shared**
|
|
245
|
+
* the **values are local**
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# developer A
|
|
249
|
+
envctl add API_KEY sk-abc123
|
|
250
|
+
git add .envctl.schema.yaml
|
|
251
|
+
git commit -m "require API_KEY"
|
|
252
|
+
|
|
253
|
+
# developer B
|
|
254
|
+
git pull
|
|
255
|
+
envctl check # shows what's missing
|
|
256
|
+
envctl fill # only asks for missing values
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
No more guessing what goes into `.env`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## CI workflow
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
ENVCTL_RUNTIME_MODE=ci envctl check
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
In CI mode:
|
|
270
|
+
|
|
271
|
+
* validation works
|
|
272
|
+
* mutations are blocked (`add`, `set`, `fill`, etc.)
|
|
273
|
+
|
|
274
|
+
You can also combine it with profiles:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
ENVCTL_PROFILE=ci ENVCTL_RUNTIME_MODE=ci envctl check
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Common commands
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# validation and visibility
|
|
286
|
+
envctl check
|
|
287
|
+
envctl inspect
|
|
288
|
+
envctl explain DATABASE_URL
|
|
289
|
+
envctl status
|
|
290
|
+
envctl doctor
|
|
291
|
+
|
|
292
|
+
# values
|
|
293
|
+
envctl add DATABASE_URL <value>
|
|
294
|
+
envctl add TEST_JSON '{"key":"value"}' --type string --format json
|
|
295
|
+
envctl set PORT 4000
|
|
296
|
+
envctl unset PORT
|
|
297
|
+
envctl remove PORT
|
|
298
|
+
|
|
299
|
+
# run / output
|
|
300
|
+
envctl run -- <command>
|
|
301
|
+
envctl sync
|
|
302
|
+
envctl export
|
|
303
|
+
|
|
304
|
+
# profiles
|
|
305
|
+
envctl profile list
|
|
306
|
+
envctl profile create staging
|
|
307
|
+
envctl profile copy local staging
|
|
308
|
+
envctl profile remove staging --yes
|
|
309
|
+
|
|
310
|
+
# vault
|
|
311
|
+
envctl vault show
|
|
312
|
+
envctl vault check
|
|
313
|
+
envctl vault path
|
|
314
|
+
envctl vault prune
|
|
315
|
+
|
|
316
|
+
# project identity
|
|
317
|
+
envctl project bind <id>
|
|
318
|
+
envctl project rebind
|
|
319
|
+
envctl project repair
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Machine-readable output
|
|
325
|
+
|
|
326
|
+
All read commands support `--json`:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
envctl --json check
|
|
330
|
+
envctl --json status
|
|
331
|
+
envctl --json inspect
|
|
332
|
+
envctl --json doctor
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Structured string validation
|
|
338
|
+
|
|
339
|
+
If a variable is a string but carries structured content, declare that semantic format in the contract:
|
|
340
|
+
|
|
341
|
+
```yaml
|
|
342
|
+
variables:
|
|
343
|
+
TEST_JSON:
|
|
344
|
+
type: string
|
|
345
|
+
format: json
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Supported `format` values for `type: string`:
|
|
349
|
+
|
|
350
|
+
* `json`
|
|
351
|
+
* `url`
|
|
352
|
+
* `csv`
|
|
353
|
+
|
|
354
|
+
When `format` is declared, `check`, `inspect`, and runtime resolution validate payload semantics, not only raw string presence.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Design principles
|
|
359
|
+
|
|
360
|
+
* Contract-first: the repo defines requirements
|
|
361
|
+
* Deterministic: same inputs → same result
|
|
362
|
+
* Explicit: nothing happens automatically
|
|
363
|
+
* Local-first: no required cloud
|
|
364
|
+
* Generated files are disposable
|
|
365
|
+
* Profiles are value namespaces, not variants
|
|
366
|
+
* CI mode is policy, not a profile
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Security model
|
|
371
|
+
|
|
372
|
+
* The contract contains **no secrets**
|
|
373
|
+
* Secrets stay on your machine
|
|
374
|
+
* `.env.local` is optional and disposable
|
|
375
|
+
* Sensitive values are masked in output
|
|
376
|
+
* Read-only commands never change state
|
|
377
|
+
* Vault files use restrictive permissions (`0600`)
|
|
378
|
+
|
|
379
|
+
Important:
|
|
380
|
+
|
|
381
|
+
> envctl assumes a trusted machine.
|
|
382
|
+
> If your machine is compromised, your secrets are compromised.
|
|
383
|
+
|
|
384
|
+
It’s not a replacement for a team-wide secrets manager.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Documentation
|
|
389
|
+
|
|
390
|
+
* [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)
|
|
391
|
+
* [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)
|
|
392
|
+
* [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)
|
|
393
|
+
* [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)
|
|
394
|
+
* [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)
|
|
395
|
+
* [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)
|
|
396
|
+
* [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)
|
|
397
|
+
* [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)
|