@romaintaillandier1978/dotenv-never-lies 0.3.0 → 1.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 +437 -160
- package/dist/cli/commands/assert.d.ts +4 -2
- package/dist/cli/commands/assert.d.ts.map +1 -1
- package/dist/cli/commands/assert.js +7 -9
- package/dist/cli/commands/explain.d.ts +6 -1
- package/dist/cli/commands/explain.d.ts.map +1 -1
- package/dist/cli/commands/explain.js +9 -16
- package/dist/cli/commands/export.d.ts +35 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +268 -0
- package/dist/cli/commands/generate.d.ts +10 -2
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +9 -7
- package/dist/cli/commands/reverseEnv.d.ts +8 -2
- package/dist/cli/commands/reverseEnv.d.ts.map +1 -1
- package/dist/cli/commands/reverseEnv.js +21 -8
- package/dist/cli/index.js +323 -99
- package/dist/cli/utils/exitCodes.d.ts +8 -0
- package/dist/cli/utils/exitCodes.d.ts.map +1 -0
- package/dist/cli/utils/exitCodes.js +8 -0
- package/dist/cli/utils/infer-schema.d.ts.map +1 -1
- package/dist/cli/utils/infer-schema.js +4 -3
- package/dist/cli/utils/load-schema.d.ts.map +1 -1
- package/dist/cli/utils/load-schema.js +20 -12
- package/dist/cli/utils/printer.d.ts.map +1 -1
- package/dist/cli/utils/printer.js +5 -4
- package/dist/cli/utils/resolve-schema.d.ts.map +1 -1
- package/dist/cli/utils/resolve-schema.js +7 -3
- package/dist/cli/utils/toFile.d.ts +2 -0
- package/dist/cli/utils/toFile.d.ts.map +1 -0
- package/dist/cli/utils/toFile.js +8 -0
- package/dist/core.d.ts +33 -33
- package/dist/core.js +10 -10
- package/dist/errors.d.ts +29 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +33 -4
- package/dist/romaintaillandier1978-dotenv-never-lies-0.3.0.tgz +0 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,96 +1,90 @@
|
|
|
1
1
|
# dotenv-never-lies
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Because environment variables lie all the time.
|
|
4
4
|
|
|
5
|
-
**dotenv-never-lies**
|
|
6
|
-
|
|
5
|
+
**dotenv-never-lies** validates, types, and documents your environment variables from a TypeScript / Zod schema.
|
|
6
|
+
It fails fast, loud, and before production.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Pourquoi ?
|
|
8
|
+
## Why?
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
Because all of this happens all the time:
|
|
13
11
|
|
|
14
|
-
- ❌
|
|
15
|
-
- ❌
|
|
16
|
-
- ❌
|
|
17
|
-
- ❌
|
|
12
|
+
- ❌ a missing env variable → runtime crash
|
|
13
|
+
- ❌ a malformed URL → subtle production bug
|
|
14
|
+
- ❌ CI was not updated after a new variable → confusing red deployment
|
|
15
|
+
- ❌ an optimistic `process.env.FOO!` → lying to yourself
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
And because `.env` files are:
|
|
20
18
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
19
|
+
- untyped
|
|
20
|
+
- undocumented
|
|
21
|
+
- shared manually
|
|
22
|
+
- rarely up to date
|
|
25
23
|
|
|
26
|
-
👉 **dotenv-never-lies**
|
|
24
|
+
👉 **dotenv-never-lies** turns this fragile configuration into an explicit contract.
|
|
27
25
|
|
|
28
26
|
---
|
|
29
27
|
|
|
30
|
-
##
|
|
28
|
+
## What the library does
|
|
31
29
|
|
|
32
|
-
- ✅
|
|
33
|
-
- ✅
|
|
34
|
-
- ✅
|
|
35
|
-
- ✅
|
|
36
|
-
- ✅
|
|
30
|
+
- ✅ validates environment variables at startup
|
|
31
|
+
- ✅ provides reliable TypeScript typings
|
|
32
|
+
- ✅ documents each variable
|
|
33
|
+
- ✅ exposes a CLI for CI and humans
|
|
34
|
+
- ✅ enables complex transformations (arrays, parsing, coercion…)
|
|
37
35
|
|
|
38
36
|
---
|
|
39
37
|
|
|
40
|
-
##
|
|
38
|
+
## What dotenv-never-lies is not
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
This package has a deliberately limited scope.
|
|
43
41
|
|
|
44
|
-
- ❌ **
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
- ❌ **It is not a frontend tool**
|
|
43
|
+
It is not meant to be used in a browser.
|
|
44
|
+
No bundler, no `import.meta.env`, no variables exposed to the client.
|
|
47
45
|
|
|
48
|
-
- ❌ **
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
- ❌ **It is not a secrets manager**
|
|
47
|
+
It does not encrypt anything, does not store anything, and does not replace Vault, AWS Secrets Manager,
|
|
48
|
+
nor your CI/CD provider’s secure variables.
|
|
51
49
|
|
|
52
|
-
- ❌ **
|
|
53
|
-
|
|
54
|
-
Deno, Bun, Cloudflare Workers, edge runtimes
|
|
50
|
+
- ❌ **It is not a cross-runtime solution**
|
|
51
|
+
Targeted support: **Node.js**.
|
|
52
|
+
Deno, Bun, Cloudflare Workers, edge runtimes: out of scope (for now).
|
|
55
53
|
|
|
56
|
-
- ❌ **
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
- ❌ **It is not a global configuration framework**
|
|
55
|
+
It does not manage YAML/JSON files, dynamic profiles,
|
|
56
|
+
nor magical per-environment overrides.
|
|
59
57
|
|
|
60
|
-
- ❌ **
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
- ❌ **It is not permissive**
|
|
59
|
+
If a variable is missing or a value is invalid, it crashes.
|
|
60
|
+
That’s the point.
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
**dotenv-never-lies**
|
|
66
|
-
|
|
62
|
+
In short:
|
|
63
|
+
**dotenv-never-lies** is for **Node.js APIs** and **backend services**
|
|
64
|
+
that prefer to **fail cleanly at startup** rather than **silently break in production**.
|
|
67
65
|
|
|
68
66
|
---
|
|
69
67
|
|
|
70
|
-
## Dependency warnings
|
|
71
|
-
|
|
72
|
-
⚠️ Important
|
|
73
|
-
dotenv-never-lies expose des schémas Zod dans son API publique.
|
|
74
|
-
Zod v4 est requis.
|
|
75
|
-
Mélanger les versions cassera l’inférence de types (et oui, ça fait mal).
|
|
76
|
-
|
|
77
68
|
## Installation
|
|
78
69
|
|
|
79
70
|
```bash
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
|
|
71
|
+
npm install @romaintaillandier1978/dotenv-never-lies
|
|
72
|
+
# or
|
|
73
|
+
yarn add @romaintaillandier1978/dotenv-never-lies
|
|
83
74
|
```
|
|
84
75
|
|
|
85
|
-
##
|
|
76
|
+
## Dependencies and compatibility
|
|
77
|
+
|
|
78
|
+
**[`zod`](https://www.npmjs.com/package/zod)** — dotenv-never-lies exposes Zod schemas in its public API.
|
|
86
79
|
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
⚠️ Important: Zod **v4.2.1** minimum is required.
|
|
81
|
+
Using Zod v3 will cause typing or inference errors.
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
sans duplication ni copier-coller fragile.
|
|
83
|
+
**[`dotenv`](https://www.npmjs.com/package/dotenv)** allows dotenv-never-lies to automatically handle parsing of env files.
|
|
92
84
|
|
|
93
|
-
|
|
85
|
+
**[`dotenv-expand`](https://www.npmjs.com/package/dotenv-expand)** allows dotenv-never-lies to automatically handle environment variable expansion. This lets you define variables composed from other variables without duplication or fragile copy-paste.
|
|
86
|
+
|
|
87
|
+
**Example**
|
|
94
88
|
|
|
95
89
|
```env
|
|
96
90
|
FRONT_A=https://a.site.com
|
|
@@ -100,51 +94,55 @@ FRONT_C=https://c.site.com
|
|
|
100
94
|
NODE_CORS_ORIGIN="${FRONT_A};${FRONT_B};${FRONT_C}"
|
|
101
95
|
```
|
|
102
96
|
|
|
103
|
-
##
|
|
97
|
+
## DNL schema
|
|
98
|
+
|
|
99
|
+
The DNL schema is your new source of truth.
|
|
100
|
+
|
|
101
|
+
(`dnl reverse-env` will help you scaffold the first skeleton)
|
|
102
|
+
|
|
103
|
+
### schema location
|
|
104
104
|
|
|
105
|
-
env.dnl.ts
|
|
105
|
+
Recommended: `env.dnl.ts`
|
|
106
|
+
|
|
107
|
+
Supported in this order for all CLI commands:
|
|
108
|
+
|
|
109
|
+
1. `--schema path/to/my-dnl.ts`
|
|
110
|
+
2. declared in `package.json`:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
...
|
|
115
|
+
"dotenv-never-lies": {
|
|
116
|
+
"schema": "path/to/my-dnl.ts"
|
|
117
|
+
}
|
|
118
|
+
...
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. one of `env.dnl.ts`, `env.dnl.js`, `dnl.config.ts`, `dnl.config.js`
|
|
123
|
+
|
|
124
|
+
### define a schema
|
|
106
125
|
|
|
107
126
|
```typescript
|
|
108
127
|
import { z } from "zod";
|
|
109
|
-
import { define } from "dotenv-never-lies";
|
|
128
|
+
import { define } from "@romaintaillandier1978/dotenv-never-lies";
|
|
110
129
|
|
|
111
130
|
export default define({
|
|
112
131
|
NODE_ENV: {
|
|
113
|
-
description: "
|
|
132
|
+
description: "Runtime environment",
|
|
114
133
|
schema: z.enum(["test", "development", "staging", "production"]),
|
|
115
134
|
},
|
|
116
135
|
|
|
117
136
|
NODE_PORT: {
|
|
118
|
-
description: "
|
|
119
|
-
schema: z.coerce.number(),
|
|
137
|
+
description: "API port",
|
|
138
|
+
schema: z.coerce.number().default(3000),
|
|
120
139
|
},
|
|
121
140
|
|
|
122
|
-
|
|
123
|
-
description: "
|
|
141
|
+
FRONT_URL: {
|
|
142
|
+
description: "My website",
|
|
124
143
|
schema: z.url(),
|
|
125
144
|
},
|
|
126
145
|
|
|
127
|
-
FRONT_B: {
|
|
128
|
-
description: "Mon site B",
|
|
129
|
-
schema: z.url(),
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
FRONT_C: {
|
|
133
|
-
description: "Mon site C",
|
|
134
|
-
schema: z.url(),
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
NODE_CORS_ORIGIN: {
|
|
138
|
-
description: "URLs frontend autorisées à appeler cette API",
|
|
139
|
-
schema: z.string().transform((v) =>
|
|
140
|
-
v
|
|
141
|
-
.split(";")
|
|
142
|
-
.map((s) => s.trim())
|
|
143
|
-
.filter(Boolean)
|
|
144
|
-
.map((url) => z.url().parse(url))
|
|
145
|
-
),
|
|
146
|
-
},
|
|
147
|
-
|
|
148
146
|
JWT_SECRET: {
|
|
149
147
|
description: "JWT Secret",
|
|
150
148
|
schema: z.string(),
|
|
@@ -153,153 +151,285 @@ export default define({
|
|
|
153
151
|
});
|
|
154
152
|
```
|
|
155
153
|
|
|
156
|
-
##
|
|
154
|
+
## Secrets handling
|
|
155
|
+
|
|
156
|
+
Reminder: dotenv-never-lies is not a secrets manager.
|
|
157
|
+
|
|
158
|
+
### declaration in the DNL schema
|
|
159
|
+
|
|
160
|
+
A variable is considered secret if and only if it is explicitly marked in the schema with `secret: true`.
|
|
161
|
+
(`secret: undefined` is equivalent to `secret: false`)
|
|
162
|
+
This rule is intentionally strict.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
JWT_SECRET: {
|
|
166
|
+
description: "JWT signing key",
|
|
167
|
+
schema: z.string(),
|
|
168
|
+
secret: true,
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Secrets and CLI commands
|
|
173
|
+
|
|
174
|
+
assert: validates secrets like any other variable
|
|
175
|
+
|
|
176
|
+
reverse-env: when generating the schema, with `--guess-secret` option, the command tries to automatically identify sensitive variables (e.g. SECRET, KEY, TOKEN, PASSWORD).
|
|
177
|
+
**This detection is heuristic and must always be reviewed and corrected manually.**
|
|
178
|
+
|
|
179
|
+
export: adapts behavior depending on the target format (env, docker, CI, Kubernetes…). See the table below for details by format.
|
|
180
|
+
|
|
181
|
+
### During export
|
|
182
|
+
|
|
183
|
+
Variables marked `secret: true` in the schema are treated differently depending on the export format.
|
|
184
|
+
|
|
185
|
+
| Format | Secrets included by default | Maskable (`--hide-secret`) | Excludable (`--exclude-secret`) | Notes |
|
|
186
|
+
| ------------- | --------------------------- | -------------------------- | ------------------------------- | -------------------------- |
|
|
187
|
+
| env | yes | yes | yes | classic .env |
|
|
188
|
+
| docker-env | yes | yes | yes | For --env-file |
|
|
189
|
+
| docker-args | yes | yes | yes | For docker run -e |
|
|
190
|
+
| json | yes | yes | yes | Debug / tooling |
|
|
191
|
+
| ts | yes | yes | yes | Typed export |
|
|
192
|
+
| js | yes | yes | yes | Runtime export |
|
|
193
|
+
| github-env | yes | yes | yes | visible in logs |
|
|
194
|
+
| github-secret | secrets only | no | yes | Via gh secret set |
|
|
195
|
+
| gitlab-env | yes | yes | yes | GitLab CI variables |
|
|
196
|
+
| k8s-configmap | yes | yes | yes | warning if secret unmasked |
|
|
197
|
+
| k8s-secret | secrets only | yes | yes | Kubernetes Secret |
|
|
198
|
+
|
|
199
|
+
## Variable lifecycle
|
|
200
|
+
|
|
201
|
+
dotenv-never-lies works with three distinct representations of environment variables:
|
|
202
|
+
|
|
203
|
+
1. **Raw value**
|
|
204
|
+
The original value coming from the source (`.env` file or `process.env`).
|
|
205
|
+
Always a string (or undefined).
|
|
206
|
+
|
|
207
|
+
2. **Runtime value (validated)**
|
|
208
|
+
The value after validation — and possible transformation — by the Zod schema.
|
|
209
|
+
This value may be strongly typed (number, boolean, array, object, etc.).
|
|
210
|
+
|
|
211
|
+
3. **Exported value**
|
|
212
|
+
The value written by `dnl export`.
|
|
213
|
+
- For env-like formats (`env`, `docker-*`, `github-*`, `k8s-*`), this is the **raw value**, after validation.
|
|
214
|
+
- For `js`, `ts` and `json`, the runtime value can be exported using `--serialize-typed`.
|
|
215
|
+
|
|
216
|
+
This separation ensures that validation, runtime usage and configuration export remain explicit and predictable.
|
|
217
|
+
|
|
218
|
+
## Runtime usage
|
|
157
219
|
|
|
158
220
|
```typescript
|
|
159
221
|
import envDef from "./env.dnl";
|
|
160
222
|
|
|
161
223
|
export const ENV = envDef.load();
|
|
162
224
|
|
|
163
|
-
if(
|
|
164
|
-
|
|
225
|
+
// if (process.env.NODE_ENV && process.env.NODE_ENV === "test") {
|
|
226
|
+
if (ENV.NODE_ENV === "test") {
|
|
227
|
+
doAdditionalTest();
|
|
228
|
+
}
|
|
165
229
|
|
|
230
|
+
const server = http.createServer(app);
|
|
231
|
+
//server.listen(process.env.NODE_PORT||3000, () => {
|
|
232
|
+
server.listen(ENV.NODE_PORT, () => {
|
|
233
|
+
console.log(`Server started on ${ENV.NODE_PORT}`);
|
|
234
|
+
});
|
|
166
235
|
```
|
|
167
236
|
|
|
168
|
-
|
|
237
|
+
Result:
|
|
169
238
|
|
|
170
|
-
- ENV.NODE_ENV
|
|
171
|
-
- ENV.NODE_PORT
|
|
172
|
-
-
|
|
173
|
-
- ENV.
|
|
174
|
-
- ENV.JWT_SECRET est une string
|
|
239
|
+
- `ENV.NODE_ENV` is an enum
|
|
240
|
+
- `ENV.NODE_PORT` is a number
|
|
241
|
+
- `FRONT_URL` is a valid URL
|
|
242
|
+
- `ENV.JWT_SECRET` is a string
|
|
175
243
|
|
|
176
|
-
|
|
177
|
-
|
|
244
|
+
If a variable is missing or invalid → the process exits immediately.
|
|
245
|
+
This is intentional.
|
|
178
246
|
|
|
179
|
-
##
|
|
247
|
+
## Avoid `process.env` in application code
|
|
180
248
|
|
|
181
|
-
|
|
182
|
-
|
|
249
|
+
Once the schema is loaded, environment variables
|
|
250
|
+
must be accessed exclusively via the `ENV` object.
|
|
183
251
|
|
|
184
|
-
|
|
252
|
+
This guarantees:
|
|
185
253
|
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
254
|
+
- strict typing
|
|
255
|
+
- validated values
|
|
256
|
+
- a single entry point for configuration
|
|
189
257
|
|
|
190
|
-
|
|
258
|
+
To identify residual `process.env` usages in your codebase, a simple search tool is enough:
|
|
191
259
|
|
|
192
260
|
```bash
|
|
193
261
|
grep -R "process\.env" src
|
|
194
262
|
```
|
|
195
263
|
|
|
196
|
-
|
|
264
|
+
Choosing to refactor (or not) those usages depends on context and is intentionally left to the developer.
|
|
197
265
|
|
|
198
266
|
## CLI
|
|
199
267
|
|
|
200
|
-
|
|
268
|
+
The CLI lets you validate, load, generate, export, and document environment variables from a `dotenv-never-lies` schema.
|
|
269
|
+
|
|
270
|
+
It is designed to be used:
|
|
201
271
|
|
|
202
|
-
|
|
272
|
+
- locally (by humans)
|
|
273
|
+
- in CI (without surprises)
|
|
274
|
+
- before the application starts (not after)
|
|
203
275
|
|
|
204
|
-
|
|
205
|
-
- en CI (sans surprise)
|
|
206
|
-
- avant que l’application ne démarre (et pas après)
|
|
276
|
+
### Exit codes
|
|
207
277
|
|
|
208
|
-
|
|
278
|
+
`dotenv-never-lies` uses explicit exit codes, designed for CI:
|
|
209
279
|
|
|
210
|
-
|
|
280
|
+
| Code | Meaning |
|
|
281
|
+
| ---: | ----------------------------- |
|
|
282
|
+
| 0 | Success |
|
|
283
|
+
| 1 | Usage error or internal error |
|
|
284
|
+
| 2 | DNL schema not found |
|
|
285
|
+
| 3 | Environment validation failed |
|
|
286
|
+
| 4 | Error during export |
|
|
287
|
+
|
|
288
|
+
### assert: Validate a `.env` file (CI-friendly)
|
|
289
|
+
|
|
290
|
+
Validates variables without injecting them into `process.env`.
|
|
211
291
|
|
|
212
292
|
```bash
|
|
213
|
-
dnl
|
|
293
|
+
dnl assert --source .env --schema env.dnl.ts
|
|
214
294
|
```
|
|
215
295
|
|
|
216
|
-
|
|
296
|
+
Without `--source`, `dnl assert` validates `process.env`.
|
|
297
|
+
This is the recommended mode when variables are injected by the runtime or CI.
|
|
217
298
|
|
|
218
|
-
|
|
219
|
-
- une valeur est invalide
|
|
220
|
-
- le schéma n’est pas respecté
|
|
299
|
+
→ fails if:
|
|
221
300
|
|
|
222
|
-
|
|
301
|
+
- a variable is missing
|
|
302
|
+
- a value is invalid
|
|
303
|
+
- the schema is not respected
|
|
223
304
|
|
|
224
|
-
|
|
305
|
+
### generate: Generate a .env file from the schema
|
|
306
|
+
|
|
307
|
+
Generates a documented `.env` from the schema.
|
|
225
308
|
|
|
226
309
|
```bash
|
|
227
|
-
dnl
|
|
310
|
+
dnl generate --schema env.dnl.ts --out .env
|
|
228
311
|
```
|
|
229
312
|
|
|
230
|
-
|
|
313
|
+
Useful for:
|
|
231
314
|
|
|
232
|
-
|
|
315
|
+
- bootstrapping a project
|
|
316
|
+
- sharing a template
|
|
317
|
+
- avoiding obsolete `.env.example` files
|
|
233
318
|
|
|
234
|
-
|
|
319
|
+
### reverse-env: Generate a schema from an existing .env
|
|
320
|
+
|
|
321
|
+
Creates an `env.dnl.ts` file from a `.env`.
|
|
235
322
|
|
|
236
323
|
```bash
|
|
237
|
-
dnl
|
|
324
|
+
dnl reverse-env --source .env
|
|
238
325
|
```
|
|
239
326
|
|
|
240
|
-
|
|
327
|
+
Useful for:
|
|
241
328
|
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
- éviter les .env.example obsolètes
|
|
329
|
+
- migrating an existing project
|
|
330
|
+
- documenting a legacy configuration afterwards
|
|
245
331
|
|
|
246
|
-
###
|
|
332
|
+
### explain: Display variables documentation
|
|
247
333
|
|
|
248
|
-
|
|
334
|
+
Displays the list of known variables and their description.
|
|
249
335
|
|
|
250
336
|
```bash
|
|
251
|
-
dnl
|
|
337
|
+
dnl explain
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Sample output:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
NODE_ENV: Runtime environment
|
|
344
|
+
NODE_PORT: API port
|
|
345
|
+
FRONT_URL: My website
|
|
346
|
+
JWT_SECRET: JWT Secret
|
|
252
347
|
```
|
|
253
348
|
|
|
254
|
-
|
|
349
|
+
### export: Export variables to other formats
|
|
350
|
+
|
|
351
|
+
The `export` command transforms variables validated by the schema
|
|
352
|
+
into formats directly consumable by other tools (Docker, CI, Kubernetes, scripts…).
|
|
255
353
|
|
|
256
|
-
|
|
257
|
-
|
|
354
|
+
The schema remains the source of truth.
|
|
355
|
+
Values are validated before export.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
dnl export <format>
|
|
359
|
+
```
|
|
258
360
|
|
|
259
|
-
|
|
361
|
+
By default, values are read from `process.env`.
|
|
362
|
+
A `.env` file can be provided via `--source`.
|
|
260
363
|
|
|
261
|
-
|
|
364
|
+
Examples:
|
|
365
|
+
Export environment variables as JSON from a `.env` file
|
|
262
366
|
|
|
263
367
|
```bash
|
|
264
|
-
dnl
|
|
368
|
+
dnl export json --source .env
|
|
265
369
|
```
|
|
266
370
|
|
|
267
|
-
|
|
371
|
+
Clean a `.env` file (remove comments and extraneous lines)
|
|
268
372
|
|
|
269
373
|
```bash
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
374
|
+
dnl export env --source .env --out .env.clean
|
|
375
|
+
dnl export env --source .env --out .env --force
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Export variables as `docker-args`
|
|
275
379
|
|
|
380
|
+
```bash
|
|
381
|
+
dnl export docker-args --source .env
|
|
276
382
|
```
|
|
277
383
|
|
|
278
|
-
|
|
384
|
+
Result:
|
|
279
385
|
|
|
280
|
-
|
|
386
|
+
```bash
|
|
387
|
+
-e "NODE_ENV=production" -e "NODE_PORT=3000"
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Export for GitHub Actions (variables)
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
dnl export github-env
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Result:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
printf '%s\n' "NODE_ENV=production" >> $GITHUB_ENV
|
|
400
|
+
printf '%s\n' "NODE_PORT=3000" >> $GITHUB_ENV
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
There are a few more formats and options (see CLI docs `dnl export --help`).
|
|
404
|
+
|
|
405
|
+
## Real-life usage
|
|
406
|
+
|
|
407
|
+
### GitIgnore
|
|
408
|
+
|
|
409
|
+
dotenv-never-lies creates temporary files in your project directory.
|
|
410
|
+
Add `.dnl/` to your `.gitignore`.
|
|
281
411
|
|
|
282
412
|
### Git
|
|
283
413
|
|
|
284
|
-
#### Git hooks
|
|
414
|
+
#### Recommended Git hooks
|
|
285
415
|
|
|
286
|
-
|
|
416
|
+
Using **dotenv-never-lies** via Git hooks is strongly recommended:
|
|
287
417
|
|
|
288
|
-
- **pre-commit
|
|
289
|
-
- **post-merge
|
|
418
|
+
- **pre-commit**: prevents committing if the local configuration is not compliant with the schema
|
|
419
|
+
- **post-merge**: immediately detects schema changes impacting the local environment
|
|
290
420
|
|
|
291
|
-
|
|
292
|
-
**
|
|
421
|
+
The goal is simple:
|
|
422
|
+
**if the local configuration is not compliant with the schema, code must not be committed.**
|
|
293
423
|
|
|
294
|
-
|
|
424
|
+
The schema is the source of truth, not `.env` files.
|
|
295
425
|
|
|
296
|
-
|
|
426
|
+
These hooks help avoid classic mistakes:
|
|
297
427
|
|
|
298
|
-
- variable
|
|
299
|
-
- format
|
|
300
|
-
-
|
|
428
|
+
- missing variable after a pull
|
|
429
|
+
- invalid format detected too late
|
|
430
|
+
- “works on my machine” due to an outdated `.env`
|
|
301
431
|
|
|
302
|
-
####
|
|
432
|
+
#### Hooks installation
|
|
303
433
|
|
|
304
434
|
```bash
|
|
305
435
|
git config core.hooksPath .githooks
|
|
@@ -317,3 +447,150 @@ EOF
|
|
|
317
447
|
|
|
318
448
|
chmod +x .githooks/pre-commit .githooks/post-merge
|
|
319
449
|
```
|
|
450
|
+
|
|
451
|
+
### GitLab CI
|
|
452
|
+
|
|
453
|
+
Environment variables validation step.
|
|
454
|
+
|
|
455
|
+
```yaml
|
|
456
|
+
# .gitlab-ci.yml
|
|
457
|
+
check-env:
|
|
458
|
+
stage: test
|
|
459
|
+
image: node:20-alpine
|
|
460
|
+
script:
|
|
461
|
+
- corepack enable
|
|
462
|
+
- yarn install --frozen-lockfile
|
|
463
|
+
- yarn dnl assert --source $DOT_ENV_FILE
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### GitHub Actions
|
|
467
|
+
|
|
468
|
+
```yaml
|
|
469
|
+
# .github/workflows/check-env.yml
|
|
470
|
+
name: Check environment
|
|
471
|
+
|
|
472
|
+
on: [push, pull_request]
|
|
473
|
+
|
|
474
|
+
jobs:
|
|
475
|
+
check-env:
|
|
476
|
+
runs-on: ubuntu-latest
|
|
477
|
+
steps:
|
|
478
|
+
- uses: actions/checkout@v4
|
|
479
|
+
|
|
480
|
+
- uses: actions/setup-node@v4
|
|
481
|
+
with:
|
|
482
|
+
node-version: 20
|
|
483
|
+
|
|
484
|
+
- run: corepack enable
|
|
485
|
+
- run: yarn install --frozen-lockfile
|
|
486
|
+
|
|
487
|
+
# Example with a .env file provided by a secret
|
|
488
|
+
- run: yarn dnl assert --source .env
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
The `.env` file can be generated from a GitHub secret or mounted dynamically.
|
|
492
|
+
|
|
493
|
+
```yaml
|
|
494
|
+
- run: echo "$ENV_FILE_CONTENT" > .env
|
|
495
|
+
env:
|
|
496
|
+
ENV_FILE_CONTENT: ${{ secrets.ENV_FILE }}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Which commands should I use?
|
|
500
|
+
|
|
501
|
+
| Situation | Command to use |
|
|
502
|
+
| --------------------------------------: | ------------------------------ |
|
|
503
|
+
| New project | generate |
|
|
504
|
+
| Existing project with a .env | reverse-env |
|
|
505
|
+
| Validate configuration in CI | assert |
|
|
506
|
+
| Validate config injected by the runtime | assert |
|
|
507
|
+
| Document variables | explain |
|
|
508
|
+
| Generate a clean .env | export env |
|
|
509
|
+
| Prepare a Docker build | export docker-\* |
|
|
510
|
+
| Inject variables in CI | export github-env / gitlab-env |
|
|
511
|
+
| Kubernetes (ConfigMap / Secret) | export k8s-\* |
|
|
512
|
+
|
|
513
|
+
Simple rule:
|
|
514
|
+
|
|
515
|
+
> The schema is always the source of truth.
|
|
516
|
+
> Commands only validate, document, or transform.
|
|
517
|
+
|
|
518
|
+
## FAQ / Design choices
|
|
519
|
+
|
|
520
|
+
### Why so strict?
|
|
521
|
+
|
|
522
|
+
Because configuration errors are bugs, not warnings.
|
|
523
|
+
|
|
524
|
+
If a variable is missing or invalid:
|
|
525
|
+
|
|
526
|
+
- the application must not start
|
|
527
|
+
- the error must be immediate and explicit
|
|
528
|
+
|
|
529
|
+
Tolerating an invalid config is just moving the bug to production.
|
|
530
|
+
|
|
531
|
+
### Why Node.js only?
|
|
532
|
+
|
|
533
|
+
Because the target runtime is clear:
|
|
534
|
+
|
|
535
|
+
- APIs
|
|
536
|
+
- workers
|
|
537
|
+
- jobs
|
|
538
|
+
- CI
|
|
539
|
+
|
|
540
|
+
Edge runtimes (Deno, Bun, Cloudflare Workers…) have:
|
|
541
|
+
|
|
542
|
+
- different environment models
|
|
543
|
+
- different constraints
|
|
544
|
+
- different expectations
|
|
545
|
+
|
|
546
|
+
They are deliberately out of scope.
|
|
547
|
+
|
|
548
|
+
### Why Zod?
|
|
549
|
+
|
|
550
|
+
Because Zod provides:
|
|
551
|
+
|
|
552
|
+
- reliable TypeScript typing
|
|
553
|
+
- consistent runtime validation
|
|
554
|
+
- expressive transformations
|
|
555
|
+
|
|
556
|
+
The schema is both:
|
|
557
|
+
|
|
558
|
+
- documentation
|
|
559
|
+
- contract
|
|
560
|
+
- validation
|
|
561
|
+
- typing source
|
|
562
|
+
|
|
563
|
+
No other tool covers these four points as cleanly today.
|
|
564
|
+
|
|
565
|
+
### Why not use dotenv-safe / env-schema / others?
|
|
566
|
+
|
|
567
|
+
These tools:
|
|
568
|
+
|
|
569
|
+
- partially validate
|
|
570
|
+
- provide little or weak typing
|
|
571
|
+
- do not really document
|
|
572
|
+
- do not offer a coherent CLI
|
|
573
|
+
|
|
574
|
+
dotenv-never-lies assumes a stricter scope,
|
|
575
|
+
but provides a full chain:
|
|
576
|
+
schema → validation → typing → CI → export.
|
|
577
|
+
|
|
578
|
+
### Why not manage secrets?
|
|
579
|
+
|
|
580
|
+
Because it is not the right level.
|
|
581
|
+
|
|
582
|
+
dotenv-never-lies:
|
|
583
|
+
|
|
584
|
+
- identifies secrets
|
|
585
|
+
- can exclude, mask, or export them
|
|
586
|
+
|
|
587
|
+
But it:
|
|
588
|
+
|
|
589
|
+
- encrypts nothing
|
|
590
|
+
- stores nothing
|
|
591
|
+
|
|
592
|
+
It integrates with existing tools; it does not compete with them.
|
|
593
|
+
|
|
594
|
+
# Conclusion:
|
|
595
|
+
|
|
596
|
+
> dotenv-never-lies does not aim to be flexible. It aims to be reliable, explicit, and predictable.
|