@modootoday/envs 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/.agent/skills/env-value-store/SKILL.md +350 -0
- package/LICENSE +93 -0
- package/NOTICE +24 -0
- package/README.md +97 -0
- package/dist/chunk-47XO3SDE.js +4591 -0
- package/dist/chunk-STHFIZRP.js +1310 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +10 -0
- package/dist/config-DKLCgBZX.d.cts +116 -0
- package/dist/config-DKLCgBZX.d.ts +116 -0
- package/dist/config.cjs +940 -0
- package/dist/config.d.cts +5 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +10 -0
- package/dist/index.cjs +5915 -0
- package/dist/index.d.cts +418 -0
- package/dist/index.d.ts +418 -0
- package/dist/index.js +112 -0
- package/package.json +98 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: env-value-store
|
|
3
|
+
description: Use when environment values are spread across several .env files, layers or machines and something reads the wrong one — a key defined twice with different values, a config file loaded as if it were env, a value that works locally and is missing in CI, or a secret that needs rotating in more than one place. Covers @modootoday/envs, which keeps the values in an encrypted catalog and answers where each one came from.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using `@modootoday/envs`
|
|
7
|
+
|
|
8
|
+
## When to reach for it
|
|
9
|
+
|
|
10
|
+
Use the catalog when multiple env files or machines make provenance, conflicts,
|
|
11
|
+
rotation and rollback hard to track. For one file in one project, dotenv is
|
|
12
|
+
usually sufficient. The parser, catalog, keyring, loader and command dispatch
|
|
13
|
+
exist; that does not establish that this package has been published to a registry.
|
|
14
|
+
|
|
15
|
+
## Install and wire
|
|
16
|
+
|
|
17
|
+
The package is not published yet. Build it in its owning repository and install
|
|
18
|
+
an approved local directory or tarball; do not assume a registry version exists.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install /path/to/approved/envs-package
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Two entries. `import { config } from "@modootoday/envs"` calls it explicitly,
|
|
25
|
+
and `import "@modootoday/envs/config"` loads on import the way `dotenv/config`
|
|
26
|
+
does; both resolve under `require` as well, so `node -r @modootoday/envs/config`
|
|
27
|
+
works. The CLI executable is `envs`.
|
|
28
|
+
|
|
29
|
+
## What the consuming repository must supply
|
|
30
|
+
|
|
31
|
+
Supply the project directory, env inputs, a supported SQLite runtime and an
|
|
32
|
+
unlock method. Keep the catalog gitignored and recovery codes outside it. For
|
|
33
|
+
CI, disable the personal global layer and declare required key names rather
|
|
34
|
+
than copying personal secrets. Backup destinations and server credentials are
|
|
35
|
+
explicit configuration, not values this package discovers by guessing.
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
### Public module and command boundary
|
|
40
|
+
|
|
41
|
+
The root exports parsing (`parseEnv`, `toRecord`), catalog and keyring operations,
|
|
42
|
+
`config`, and command dispatch. `parseEnv` returns `{ ok, entries, findings }`;
|
|
43
|
+
`toRecord` returns a record only for a valid parse and `null` otherwise. CLI help
|
|
44
|
+
comes from command definitions, so consult it rather than guessing option names.
|
|
45
|
+
|
|
46
|
+
### build is the dangerous one
|
|
47
|
+
|
|
48
|
+
It bakes values into a module for targets that cannot read a file. Default is **nothing
|
|
49
|
+
selected**, and a key not classified `low` is refused even when named — unclassified
|
|
50
|
+
reads as `medium`, so saying nothing gives the safe answer. `--allow-sensitive` exists
|
|
51
|
+
and should be argued for, not reached for: a deployed bundle cannot be recalled.
|
|
52
|
+
|
|
53
|
+
### serve hands over bytes, not values
|
|
54
|
+
|
|
55
|
+
It serves the sealed catalog. It holds no key and cannot decrypt, so a compromised server
|
|
56
|
+
gives up what a stolen catalog file would and no more. Requiring a token of real length
|
|
57
|
+
is the whole security of the endpoint, so it is required rather than generated quietly,
|
|
58
|
+
and binding to anything but localhost warns that the token crosses the wire.
|
|
59
|
+
|
|
60
|
+
`--once` stops after the catalog is **handed over**, not after any request: an
|
|
61
|
+
unauthorised probe must not end a one-shot pull before the client with the token arrives.
|
|
62
|
+
|
|
63
|
+
### Backups follow the same provider rule as the sqlite backends
|
|
64
|
+
|
|
65
|
+
Differences are data, eligibility is declared rather than discovered by failing, and a
|
|
66
|
+
pin exercises the non-default path. `s3` covers S3, R2, MinIO and Backblaze — what
|
|
67
|
+
differs between them is a hostname — and `file` writes to a directory. Neither is
|
|
68
|
+
eligible unless configured: a destination nobody set is a missing setting, not a failed
|
|
69
|
+
write.
|
|
70
|
+
|
|
71
|
+
A snapshot seals the catalog under its data key and carries that key's **wraps in the
|
|
72
|
+
header**. Without them a backup could only be opened by the catalog it came from, which
|
|
73
|
+
is the thing that may be gone — so **a recovery code alone restores**, which is the whole
|
|
74
|
+
point. The wraps are already sealed blobs, so the header leaks nothing.
|
|
75
|
+
|
|
76
|
+
SigV4 is implemented here rather than depended on, and verified against the derivation
|
|
77
|
+
vector AWS publishes. **No live endpoint was exercised**; say so rather than implying the
|
|
78
|
+
S3 path has been run against a real bucket.
|
|
79
|
+
|
|
80
|
+
- **Restore must delete the `-wal` and `-shm` beside the catalog.** Replacing only the
|
|
81
|
+
main file leaves the old journal, and sqlite replays it onto the restored database —
|
|
82
|
+
the values the snapshot was taken to undo come straight back. Measured on bun, where
|
|
83
|
+
the write had not been checkpointed; node passed by luck. It pairs with the
|
|
84
|
+
`wal_checkpoint(TRUNCATE)` backup takes before reading the file.
|
|
85
|
+
|
|
86
|
+
### The surface deliberately matches dotenvx
|
|
87
|
+
|
|
88
|
+
dotenvx, Doppler and Infisical have converged on the same shape, so this package uses it
|
|
89
|
+
rather than inventing names: `run -- <cmd>` to inject, `get`/`set`/`del` for values,
|
|
90
|
+
`ls` to list, `export --format` to take them out, plus `rotate`, `genexample`,
|
|
91
|
+
`gitignore` and `precommit`. `set` accepts both `KEY VALUE` and `KEY=VALUE` because the
|
|
92
|
+
three tools disagree about which one is canonical — but refuses them mixed, since
|
|
93
|
+
`set A=1 B=2` could be read either way.
|
|
94
|
+
|
|
95
|
+
Three deliberate differences, worth stating when a user expects dotenvx exactly:
|
|
96
|
+
|
|
97
|
+
- **No `encrypt`/`decrypt`.** dotenvx encrypts a file in place; this keeps a store, and
|
|
98
|
+
the round trip is `load` then `export`.
|
|
99
|
+
- **`get` with no key does not print everything.** dotenvx does, but that would make the
|
|
100
|
+
`export --yes` gate meaningless. Names come from `ls --keys`, values from `export`.
|
|
101
|
+
- **No `native`, `armor` or `lock`** — those are that service's own features.
|
|
102
|
+
|
|
103
|
+
`rotate` is cheap here because of the key hierarchy: it re-wraps the data key and
|
|
104
|
+
re-encrypts nothing. New wraps are written **before** the old ones are retired, so an
|
|
105
|
+
interruption leaves too many ways in rather than none.
|
|
106
|
+
|
|
107
|
+
### The shape of a change
|
|
108
|
+
|
|
109
|
+
Releases are immutable. `load` and `set` both write a **new** release carrying every
|
|
110
|
+
value with the change applied, and move the pointer; `rollback` moves the pointer back.
|
|
111
|
+
Nothing is deleted, which is what makes going back cheap and reversible. Two consequences
|
|
112
|
+
worth telling a user: `load` carries other sources forward unless `--replace`, so loading
|
|
113
|
+
one file does not empty the rest; and `set` refuses to guess which source owns a key when
|
|
114
|
+
there is more than one.
|
|
115
|
+
|
|
116
|
+
`doctor` answers where a value came from. Same key with the same value in two sources
|
|
117
|
+
warns, different values error, and neither prints a value. It also names the catalogs it
|
|
118
|
+
actually read — running in the wrong directory is the one trap this layout has.
|
|
119
|
+
|
|
120
|
+
### Losing a key does not lose the catalog
|
|
121
|
+
|
|
122
|
+
Values are sealed under a random DEK, and the DEK is wrapped once per way back: the KEK,
|
|
123
|
+
and each recovery code. Any one wrap opens it, so a lost KEK costs nothing while a
|
|
124
|
+
recovery code survives, and a new way back can be added without re-encrypting a value.
|
|
125
|
+
|
|
126
|
+
**Recovery codes are printed once at `init` and never stored** — the table holds only the
|
|
127
|
+
wrapped DEK, so it cannot give a code back. Codes are Crockford base32 (no I, L, O or U)
|
|
128
|
+
and are accepted in any case with any separators, because refusing a transcription slip
|
|
129
|
+
the alphabet was designed to absorb would mean a lost catalog.
|
|
130
|
+
|
|
131
|
+
To open a catalog with one:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
printf '%s' "$CODE" | npx @modootoday/envs export --yes --recovery-code -
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`export` is the only command that prints values, so it refuses without `--yes`, prefers
|
|
138
|
+
stdin over argv for the code, and warns when a code is passed inline where other
|
|
139
|
+
processes can see it. Its CSV quotes every field — env values really do contain commas,
|
|
140
|
+
quotes and newlines — and a value starting with `=`, `+`, `-` or `@` is **reported, not
|
|
141
|
+
rewritten**: altering it would hand back something the catalog does not hold.
|
|
142
|
+
|
|
143
|
+
## Worked examples
|
|
144
|
+
|
|
145
|
+
### Parse without changing the environment
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { parseEnv, toRecord } from "@modootoday/envs";
|
|
149
|
+
|
|
150
|
+
const parsed = parseEnv("MODE=test\n");
|
|
151
|
+
if (!parsed.ok) throw new Error("Invalid env document");
|
|
152
|
+
const values = toRecord(parsed);
|
|
153
|
+
// values is local data; no process environment or catalog has been modified.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Refuse an input that is not env format
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { parseEnv, toRecord } from "@modootoday/envs";
|
|
160
|
+
|
|
161
|
+
const parsed = parseEnv("name: service\n");
|
|
162
|
+
if (parsed.ok || toRecord(parsed) !== null) throw new Error("Expected refusal");
|
|
163
|
+
// Report parsed.findings (codes and line numbers), never candidate values.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Testing against it
|
|
167
|
+
|
|
168
|
+
### Runtime and database fixtures
|
|
169
|
+
|
|
170
|
+
The suite must pass under **both** runtimes, and running only one hides real defects.
|
|
171
|
+
`npx vitest run` uses node; `bunx --bun vitest run` uses bun.
|
|
172
|
+
|
|
173
|
+
Three sqlite backends sit behind one interface, and everything they disagree about is
|
|
174
|
+
data in `src/sqlite/provider.ts`. Do not reintroduce these differences in calling code:
|
|
175
|
+
|
|
176
|
+
- **Booleans.** node and better-sqlite3 throw on a bound boolean, bun accepts it. Bind
|
|
177
|
+
through the adapter, which normalises to 0/1.
|
|
178
|
+
- **A missing row.** bun returns `null` from `get()` and node returns `undefined`, so a
|
|
179
|
+
`!== undefined` check passes on one and dereferences null on the other. The adapter
|
|
180
|
+
returns `undefined` on both; do not compare against `null` in calling code.
|
|
181
|
+
- **Named parameters.** bun needs the sigil form (`$a`), better-sqlite3 needs the bare
|
|
182
|
+
name (`a`), node takes either. Getting it wrong is **not always an error**: bun binds
|
|
183
|
+
NULL and says nothing, so the adapter matches keys against the statement's own
|
|
184
|
+
placeholders.
|
|
185
|
+
- **Constructor options.** bun rejects `{}` and `{readonly:false}` and wants
|
|
186
|
+
`create: true`; node spells it `readOnly` and **silently ignores** the lowercase
|
|
187
|
+
spelling, handing back a writable database; better-sqlite3 wants the lowercase name.
|
|
188
|
+
Assert that a write is actually refused, never that a flag was passed.
|
|
189
|
+
- **Eligibility is checked, not attempted.** Loading better-sqlite3 under bun 1.3.14
|
|
190
|
+
panics the process with `NAPI FATAL ERROR` — no try/catch contains it. A provider
|
|
191
|
+
declares where it may run.
|
|
192
|
+
- **Import through a variable specifier.** A literal makes TypeScript try to resolve
|
|
193
|
+
`bun:sqlite` and makes bundlers treat the absent module as a hard failure.
|
|
194
|
+
|
|
195
|
+
`config()` is synchronous. Its entry point resolves the binding through
|
|
196
|
+
`createRequire` and uses `node:crypto` rather than Web Crypto. Adding an `await`
|
|
197
|
+
changes the public contract. The supported call is the named root export;
|
|
198
|
+
there is no exported `/config` side-effect subpath.
|
|
199
|
+
|
|
200
|
+
Commands are values in `src/commands/`: name, description, option specs, `run`. Help is
|
|
201
|
+
generated from the spec, so a flag cannot exist in the parser and not the help, and an
|
|
202
|
+
option the command did not declare is an **error** rather than a silently ignored typo.
|
|
203
|
+
Output goes through `src/cli/ui.ts` — values to stdout so they can be piped, everything
|
|
204
|
+
else to stderr, colour only on a TTY without `NO_COLOR`. No dependency for either.
|
|
205
|
+
|
|
206
|
+
- **Never write a control byte into a source file.** Build it from `String.fromCharCode`
|
|
207
|
+
and reference the constant. This has gone wrong twice here: a NUL in a test fixture and
|
|
208
|
+
raw escape bytes in the colour table.
|
|
209
|
+
- better-sqlite3 is an **optional peer**: not installed by this package, so its rows in
|
|
210
|
+
the backend matrix only run where someone installed it. `ENVS_SQLITE_BACKEND` pins one
|
|
211
|
+
backend when you need to exercise a specific path.
|
|
212
|
+
|
|
213
|
+
### Values are never syntax
|
|
214
|
+
|
|
215
|
+
Env values are arbitrary text from files this package does not control, so no statement
|
|
216
|
+
is ever built from data — no interpolation, no concatenation, parameters only. A guard
|
|
217
|
+
test scans every source file for both patterns and proves the detector fires on a
|
|
218
|
+
fixture. Two consequences worth keeping in mind:
|
|
219
|
+
|
|
220
|
+
- Key names reach the database as HMACs and values as sealed blobs, so even the
|
|
221
|
+
identifier surface carries no caller text.
|
|
222
|
+
- A string containing a NUL is **refused**. Measured: sqlite TEXT ends at the first NUL,
|
|
223
|
+
so `a\0b` is stored and read back as `a`, losing the rest without an error.
|
|
224
|
+
|
|
225
|
+
Do not tell a user a command works until it appears in the list above.
|
|
226
|
+
|
|
227
|
+
## Invariants
|
|
228
|
+
|
|
229
|
+
### Catalog ownership
|
|
230
|
+
|
|
231
|
+
`dotenv` reads a file and puts it in `process.env`. That is enough until there is more
|
|
232
|
+
than one file. Then nobody can answer which file supplied a value, a key defined in two
|
|
233
|
+
places silently resolves to one of them, and rotating a credential means finding every
|
|
234
|
+
copy.
|
|
235
|
+
|
|
236
|
+
This package keeps the values in a catalog — a small encrypted SQLite database — and
|
|
237
|
+
treats files as inputs to it. Because there is a store rather than a file, it can answer
|
|
238
|
+
where a value came from, hold releases and a pointer so a rollback moves the pointer, and
|
|
239
|
+
carry an audit trail.
|
|
240
|
+
|
|
241
|
+
Reach for it when:
|
|
242
|
+
|
|
243
|
+
- The same key exists in more than one file and you need to know which one wins.
|
|
244
|
+
- A value works on one machine and is missing on another.
|
|
245
|
+
- A credential must be rotated and you do not know how many copies exist.
|
|
246
|
+
- A `.env` path actually points at YAML or JSON and is being read as env anyway.
|
|
247
|
+
|
|
248
|
+
Do not reach for it to hold one `.env` in one project. `dotenv` is the right size there.
|
|
249
|
+
|
|
250
|
+
### The format rule, and why it is strict
|
|
251
|
+
|
|
252
|
+
An env document is a sequence of logical entries, each one of exactly three kinds:
|
|
253
|
+
|
|
254
|
+
1. a line starting with `#`
|
|
255
|
+
2. a blank line
|
|
256
|
+
3. `KEY=VALUE`
|
|
257
|
+
|
|
258
|
+
Entries are logical, not physical: a quoted value may span several lines. Anything that
|
|
259
|
+
is not one of the three means the file is not env format, and then **none** of it loads.
|
|
260
|
+
|
|
261
|
+
This matters because `dotenv` is lenient in ways that produce plausible wrong answers.
|
|
262
|
+
Measured against dotenv 17.4.2:
|
|
263
|
+
|
|
264
|
+
| Input | dotenv | this package |
|
|
265
|
+
| ------------------------------------------ | ------------------------------------- | ------------------- |
|
|
266
|
+
| `name: envs` over two lines (YAML) | parses into keys | rejected |
|
|
267
|
+
| `KEY: value` | `{KEY: "value"}` | rejected |
|
|
268
|
+
| `A="oops` unterminated, rest of file valid | `A` becomes `"oops`, rest still loads | whole file rejected |
|
|
269
|
+
| `{"a": 1}` (JSON) | `{}`, no error | rejected |
|
|
270
|
+
|
|
271
|
+
Pointing a loader at the wrong file should fail, not return something that looks like
|
|
272
|
+
configuration.
|
|
273
|
+
|
|
274
|
+
### Value syntax
|
|
275
|
+
|
|
276
|
+
- `VALUE` may be bare, or wrapped in `"`, `'`, or a backtick.
|
|
277
|
+
- Inside quotes, `#` and `=` are literal. A bare value ends at the first `#`.
|
|
278
|
+
- Whitespace is trimmed only from bare values.
|
|
279
|
+
- **Double quotes expand `\n` and `\r` and nothing else.** `\t`, `\\` and `\"` stay
|
|
280
|
+
literal, which is what keeps `"C:\path\to"` intact. Never parse a value with
|
|
281
|
+
`JSON.parse` — it throws on Windows paths and expands escapes dotenv does not.
|
|
282
|
+
- Keys are not required to be uppercase. `api_key=secret` is env format. Casing is a
|
|
283
|
+
convention, so `lint` mentions it and the parser does not enforce it.
|
|
284
|
+
|
|
285
|
+
### Where things live
|
|
286
|
+
|
|
287
|
+
| | Path | Note |
|
|
288
|
+
| ------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
289
|
+
| Catalog (source of truth) | `<project>/.envs/catalog.sqlite` | `~/.envs/` only when there is no project root; `ENVS_CATALOG_PATH` overrides |
|
|
290
|
+
| Cache (derived) | `node_modules/.cache/envs/` when installed, `~/.envs/cache/` under npx or a global install | Safe to delete; rebuilt from the catalog |
|
|
291
|
+
| Requirements | `envs.requires` at the project root | Committed. Names only, never values |
|
|
292
|
+
|
|
293
|
+
Two rules follow from this and are worth stating to a user before they are surprised:
|
|
294
|
+
|
|
295
|
+
- **The catalog stays with the project even under a global install.** Installing the CLI
|
|
296
|
+
with `-g` while the runtime is a devDependency is the ordinary setup; a home-directory
|
|
297
|
+
catalog would mean a value set inside a project is invisible to that project.
|
|
298
|
+
- **`.envs/` must be gitignored.** It holds envelopes, history and audit rows. `doctor`
|
|
299
|
+
reports an error, not a warning, if it is ever tracked.
|
|
300
|
+
|
|
301
|
+
### The global layer
|
|
302
|
+
|
|
303
|
+
`~/.envs` is a settings layer, not a second store. It holds what belongs to a person and
|
|
304
|
+
a machine — a personal API key, `ENVS_PROVIDER`, backup targets — and the loader resolves:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
process.env > project catalog and files > ~/.envs
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The project always wins; the global layer only fills keys the project does not define.
|
|
311
|
+
It is switched off with `config({ global: false })` or `ENVS_NO_GLOBAL=1`, and CI should
|
|
312
|
+
switch it off, because a build that passes locally on a home-directory value and fails in
|
|
313
|
+
CI is the standard failure of this pattern.
|
|
314
|
+
|
|
315
|
+
### Diagnosing, and repairing a split
|
|
316
|
+
|
|
317
|
+
`doctor` answers where each value came from and never prints a value.
|
|
318
|
+
|
|
319
|
+
When it reports keys that exist only in the global layer, there are two repairs and they
|
|
320
|
+
move different things:
|
|
321
|
+
|
|
322
|
+
- `doctor --require <KEY>` records that the key is needed, in `envs.requires`. It carries
|
|
323
|
+
no value, so it can be committed, which is what lets it reach a teammate or CI at all.
|
|
324
|
+
`config()` then throws when a required key resolves nowhere, instead of the application
|
|
325
|
+
receiving `undefined`.
|
|
326
|
+
- `doctor --adopt <KEY>` moves the value into the project. Correct only when the value was
|
|
327
|
+
project scoped and had been left global by mistake.
|
|
328
|
+
|
|
329
|
+
Prefer `--require`. Copying a value into `<project>/.envs/` makes the project
|
|
330
|
+
self-contained on one machine only — that directory is gitignored, so a teammate and CI
|
|
331
|
+
are exactly where they were. `--adopt --all` is refused by default: personal credentials
|
|
332
|
+
are what the global layer exists to hold once, and copying them per project turns one
|
|
333
|
+
rotation into many edits.
|
|
334
|
+
|
|
335
|
+
## What it will not do
|
|
336
|
+
|
|
337
|
+
It does not provision credentials, make a registry release available, or prove
|
|
338
|
+
that an S3 destination has been exercised live. Sealed data still needs an
|
|
339
|
+
external recovery path. Report the tested runtime and backend rather than
|
|
340
|
+
claiming every deployment has been validated.
|
|
341
|
+
|
|
342
|
+
### Reporting rules
|
|
343
|
+
|
|
344
|
+
When surfacing anything from this tool to a user:
|
|
345
|
+
|
|
346
|
+
- **Never print a value.** Report the key, the file, the line, and whether two values are
|
|
347
|
+
the same or different. The existing lint rule `duplicate-key-across-layers` sets the
|
|
348
|
+
convention: same value warns, different value errors, neither prints.
|
|
349
|
+
- Say which catalog was actually used. Running in a directory with no project root writes
|
|
350
|
+
to `~/.envs`, and a user who does not notice will look for the value in the wrong place.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Elastic License 2.0
|
|
2
|
+
|
|
3
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
12
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
13
|
+
available, and prepare derivative works of the software, in each case subject to
|
|
14
|
+
the limitations and conditions below.
|
|
15
|
+
|
|
16
|
+
## Limitations
|
|
17
|
+
|
|
18
|
+
You may not provide the software to third parties as a hosted or managed
|
|
19
|
+
service, where the service provides users with access to any substantial set of
|
|
20
|
+
the features or functionality of the software.
|
|
21
|
+
|
|
22
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
23
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
24
|
+
software that is protected by the license key.
|
|
25
|
+
|
|
26
|
+
You may not alter, remove, or obscure any licensing, copyright, or other notices
|
|
27
|
+
of the licensor in the software. Any use of the licensor’s trademarks is subject
|
|
28
|
+
to applicable law.
|
|
29
|
+
|
|
30
|
+
## Patents
|
|
31
|
+
|
|
32
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
33
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
34
|
+
sale, import and have imported the software, in each case subject to the
|
|
35
|
+
limitations and conditions in this license. This license does not cover any
|
|
36
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
37
|
+
the software. If you or your company make any written claim that the software
|
|
38
|
+
infringes or contributes to infringement of any patent, your patent license for
|
|
39
|
+
the software granted under these terms ends immediately. If your company makes
|
|
40
|
+
such a claim, your patent license ends immediately for work on behalf of your
|
|
41
|
+
company.
|
|
42
|
+
|
|
43
|
+
## Notices
|
|
44
|
+
|
|
45
|
+
You must ensure that anyone who gets a copy of any part of the software from you
|
|
46
|
+
also gets a copy of these terms.
|
|
47
|
+
|
|
48
|
+
If you modify the software, you must include in any modified copies of the
|
|
49
|
+
software prominent notices stating that you have modified the software.
|
|
50
|
+
|
|
51
|
+
## No Other Rights
|
|
52
|
+
|
|
53
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
54
|
+
these terms.
|
|
55
|
+
|
|
56
|
+
## Termination
|
|
57
|
+
|
|
58
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
59
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
60
|
+
with a notice of your violation, and you cease all violation of this license no
|
|
61
|
+
later than 30 days after you receive that notice, your licenses will be
|
|
62
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
63
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
64
|
+
to terminate automatically and permanently.
|
|
65
|
+
|
|
66
|
+
## No Liability
|
|
67
|
+
|
|
68
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
69
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
70
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
71
|
+
legal claim.*
|
|
72
|
+
|
|
73
|
+
## Definitions
|
|
74
|
+
|
|
75
|
+
The **licensor** is the entity offering these terms, and the **software** is the
|
|
76
|
+
software the licensor makes available under these terms, including any portion
|
|
77
|
+
of it.
|
|
78
|
+
|
|
79
|
+
**you** refers to the individual or entity agreeing to these terms.
|
|
80
|
+
|
|
81
|
+
**your company** is any legal entity, sole proprietorship, or other kind of
|
|
82
|
+
organization that you work for, plus all organizations that have control over,
|
|
83
|
+
are under the control of, or are under common control with that
|
|
84
|
+
organization. **control** means ownership of substantially all the assets of an
|
|
85
|
+
entity, or the power to direct its management and policies by vote, contract, or
|
|
86
|
+
otherwise. Control can be direct or indirect.
|
|
87
|
+
|
|
88
|
+
**your licenses** are all the licenses granted to you for the software under
|
|
89
|
+
these terms.
|
|
90
|
+
|
|
91
|
+
**use** means anything you do with the software requiring one of your licenses.
|
|
92
|
+
|
|
93
|
+
**trademark** means trademarks, service marks, and similar rights.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@modootoday/envs
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 modootoday. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Licensor: modootoday
|
|
6
|
+
Licensed under the Elastic License 2.0; see LICENSE.
|
|
7
|
+
|
|
8
|
+
The licensor is modootoday, the company, not any individual contributor.
|
|
9
|
+
This file names the licensor because the Elastic License 2.0 defines that term
|
|
10
|
+
rather than carrying a copyright line of its own; LICENSE is kept unmodified.
|
|
11
|
+
|
|
12
|
+
What the licence permits, in short, without replacing the terms in LICENSE:
|
|
13
|
+
|
|
14
|
+
- Using this software inside your own company, on any scale.
|
|
15
|
+
- Embedding it in a product you sell, and modifying or forking it.
|
|
16
|
+
- Redistributing it, with these notices intact.
|
|
17
|
+
|
|
18
|
+
What it does not permit:
|
|
19
|
+
|
|
20
|
+
- Offering this software to third parties as a hosted or managed service.
|
|
21
|
+
- Circumventing licence key functionality.
|
|
22
|
+
- Removing or obscuring these notices.
|
|
23
|
+
|
|
24
|
+
LICENSE governs. Where this summary and LICENSE differ, LICENSE is what applies.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @modootoday/envs
|
|
2
|
+
|
|
3
|
+
Environment values in an encrypted catalog rather than scattered `.env` files.
|
|
4
|
+
|
|
5
|
+
**[envs.build](https://envs.build)** has the documentation. This page is the introduction.
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
$ npx @modootoday/envs init
|
|
9
|
+
+ catalog created
|
|
10
|
+
+ added to .gitignore
|
|
11
|
+
|
|
12
|
+
Recovery codes — 5, shown once, not stored
|
|
13
|
+
1575H-MPBMP-K41AR-PXAVB-NN920-G
|
|
14
|
+
G8T2M-4KQZR-7VXWD-3NBHE-J05YA-P
|
|
15
|
+
…
|
|
16
|
+
|
|
17
|
+
$ envs load .env
|
|
18
|
+
+ .env 12 keys
|
|
19
|
+
+ release 8f21ac04 is current
|
|
20
|
+
|
|
21
|
+
$ envs run -- node server.js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What it does that a file cannot
|
|
25
|
+
|
|
26
|
+
`dotenv` reads a file into `process.env`, which is all most projects need. This is for
|
|
27
|
+
the ones with four files, three machines, and a credential that gets rotated.
|
|
28
|
+
|
|
29
|
+
- **Tells you which file set a key.** When `.env` and `.env.local` both declare
|
|
30
|
+
`DATABASE_URL`, `envs doctor` names the one that won. It prints the key and the
|
|
31
|
+
source, never the value.
|
|
32
|
+
- **Survives a lost key.** `init` prints five recovery codes once. Any one of them opens
|
|
33
|
+
the catalog on its own, and none of them is ever stored.
|
|
34
|
+
- **Refuses a file that is not env format.** `dotenv` reads a two-line YAML document as
|
|
35
|
+
environment variables. This checks first, and if the file fails, nothing loads.
|
|
36
|
+
- **Rolls back without deleting anything.** Changing a value writes a new release. Going
|
|
37
|
+
back moves a pointer.
|
|
38
|
+
|
|
39
|
+
## Migrating from dotenv
|
|
40
|
+
|
|
41
|
+
`config()` takes the same options — `path`, `encoding`, `override`, `processEnv` — and it
|
|
42
|
+
is synchronous, so the side-effect import still works.
|
|
43
|
+
|
|
44
|
+
```diff
|
|
45
|
+
- import "dotenv/config";
|
|
46
|
+
+ import "@modootoday/envs/config";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Both module systems resolve, so the preload form works without touching your code:
|
|
50
|
+
|
|
51
|
+
```console
|
|
52
|
+
$ node -r @modootoday/envs/config server.js
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
One difference worth knowing before you switch: `dotenv` returns quietly when there is no
|
|
56
|
+
file, while this throws when there is no catalog. A missing store is the failure this
|
|
57
|
+
exists to make visible, so it is loud rather than empty.
|
|
58
|
+
|
|
59
|
+
The command names follow dotenvx: `run -- cmd`, `get`, `set`, `ls`, `rotate`. If you have
|
|
60
|
+
used it, most of this is already familiar.
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
| | |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Getting started | <https://envs.build/guide/> |
|
|
67
|
+
| The format rule, measured against dotenv | <https://envs.build/format/> |
|
|
68
|
+
| Recovery codes | <https://envs.build/recovery/> |
|
|
69
|
+
| How it compares to dotenv, dotenvx, Doppler and Infisical | <https://envs.build/compare/> |
|
|
70
|
+
| Sharing a catalog, and what the server cannot see | <https://envs.build/hosted/> |
|
|
71
|
+
| All 28 commands | <https://envs.build/commands/> |
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
Node.js 22 or Bun 1.3. Nothing to sign up for, and no network call unless you ask for one.
|
|
76
|
+
|
|
77
|
+
## Before you commit to it
|
|
78
|
+
|
|
79
|
+
- **The licence is not OSI open source.** See below.
|
|
80
|
+
- **It starts slower than dotenv** — about 6.6 ms for a hundred keys against 0.2 ms.
|
|
81
|
+
- **One `.env` and one developer does not need this.** dotenv already does that job, with
|
|
82
|
+
no key to manage and nothing to back up.
|
|
83
|
+
|
|
84
|
+
## Licence
|
|
85
|
+
|
|
86
|
+
Elastic License 2.0. Licensor: **modootoday**. See [`LICENSE`](LICENSE) and
|
|
87
|
+
[`NOTICE`](NOTICE), and <https://envs.build/licence/>.
|
|
88
|
+
|
|
89
|
+
Use it inside your company at any scale, ship it in a product you sell, fork it. What you
|
|
90
|
+
cannot do is offer this software to third parties as a hosted or managed service. It
|
|
91
|
+
carries a patent grant with a retaliation clause: asserting patent claims over this
|
|
92
|
+
software ends your licence.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
Repository <https://github.com/modootoday/envs> · npm
|
|
97
|
+
[`@modootoday/envs`](https://www.npmjs.com/package/@modootoday/envs)
|