@super-repo/envx 0.2.3-b.2 → 0.2.3-b.4
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 +970 -104
- package/dist/auto.js.map +1 -1
- package/dist/chunks/commands-D3eQPQO6.js +1502 -0
- package/dist/chunks/commands-D3eQPQO6.js.map +1 -0
- package/dist/chunks/{src-CDuEfaCY.js → src-D0n2wHDg.js} +0 -0
- package/dist/chunks/src-D0n2wHDg.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/commands/audit.d.ts +13 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/bake.d.ts +18 -0
- package/dist/commands/bake.d.ts.map +1 -0
- package/dist/commands/diff.d.ts +16 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +16 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/encrypt.d.ts.map +1 -1
- package/dist/commands/hook.d.ts +18 -0
- package/dist/commands/hook.d.ts.map +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/info.d.ts +10 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/rotate.d.ts +13 -0
- package/dist/commands/rotate.d.ts.map +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/template.d.ts +13 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/types.d.ts +14 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/watch.d.ts +14 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -28
- package/dist/index.js.map +1 -1
- package/docs/auto-detection.md +217 -0
- package/docs/configuration.md +224 -0
- package/docs/recipes.md +234 -0
- package/package.json +6 -4
- package/dist/chunks/commands-B8vc6UKO.js +0 -354
- package/dist/chunks/commands-B8vc6UKO.js.map +0 -1
- package/dist/chunks/src-CDuEfaCY.js.map +0 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EAEL,UAAU,EACV,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,EAElB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,WAAW,EAEX,YAAY,EACZ,YAAY,EACZ,WAAW,EAEX,QAAQ,EACR,YAAY,EACZ,QAAQ,EAER,YAAY,EACZ,YAAY,EAEZ,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EAEjB,YAAY,EACZ,aAAa,EACb,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,eAAe,EACf,SAAS,EACT,UAAU,EACV,SAAS,EACT,aAAa,EACb,YAAY,EACZ,OAAO,EACP,MAAM,EACN,OAAO,GACR,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;GAmBG;AACH;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,cAAc,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErF,iBAAS,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC;AACnC,iBAAS,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;AAChD,iBAAS,IAAI,CAAC,IAAI,EAAE,uBAAuB,GAAG,MAAM,CAAC,UAAU,CAAC;AAiGhE,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,64 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { C as serializeEnv, D as encryptValueAsymmetric, E as decryptValueAsymmetric, O as generateKeyPair, S as parseEnv, T as ENCRYPTED_PREFIX, _ as resolveEnvPaths, a as rotateFiles, b as expandRecord, c as defaultKeysPath, f as detectEnvironment, g as resolveCwdOrWorkspace, h as loadEnv, i as auditFiles, k as isEncrypted, l as readKeysFile, n as loadDotenvxConfig, o as decryptFiles, p as findWorkspaceRoot, r as BUILT_IN_PATTERNS, s as encryptFiles, u as writeKeysFile, w as toRecord, y as expandEnvSrc } from "./chunks/src-D0n2wHDg.js";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
function envx(arg) {
|
|
4
|
-
const { config } = loadDotenvxConfig();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
...config.override !== void 0 ? { override: config.override } : {},
|
|
18
|
-
...config.quiet !== void 0 ? { quiet: config.quiet } : {}
|
|
19
|
-
};
|
|
20
|
-
else opts = {
|
|
21
|
-
envFiles: arg.envFiles ?? (config.envFiles ? [...config.envFiles] : [".env"]),
|
|
22
|
-
...arg.cascade !== void 0 ? { cascade: arg.cascade } : config.cascade !== void 0 ? { cascade: config.cascade } : {},
|
|
23
|
-
...arg.envPath !== void 0 ? { envPath: arg.envPath } : config.envPath !== void 0 ? { envPath: config.envPath } : {},
|
|
24
|
-
...arg.vault !== void 0 ? { vault: arg.vault } : {},
|
|
25
|
-
...arg.variables !== void 0 ? { variables: arg.variables } : {},
|
|
26
|
-
...arg.override !== void 0 ? { override: arg.override } : config.override !== void 0 ? { override: config.override } : {},
|
|
27
|
-
...arg.quiet !== void 0 ? { quiet: arg.quiet } : config.quiet !== void 0 ? { quiet: config.quiet } : {}
|
|
28
|
-
};
|
|
29
|
-
loadEnv(opts);
|
|
4
|
+
const { config: baseConfig } = loadDotenvxConfig();
|
|
5
|
+
const userOpts = arg === void 0 ? {} : typeof arg === "string" ? { cascade: arg } : arg;
|
|
6
|
+
let cfg = baseConfig;
|
|
7
|
+
if (userOpts.profile) {
|
|
8
|
+
const profile = cfg.profiles?.[userOpts.profile];
|
|
9
|
+
if (!profile) throw new Error(`[ENVX_UNKNOWN_PROFILE] '${userOpts.profile}' not found (available: ${Object.keys(cfg.profiles ?? {}).join(", ") || "none"})`);
|
|
10
|
+
cfg = {
|
|
11
|
+
...cfg,
|
|
12
|
+
...profile
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const { profile: _drop, ...rest } = userOpts;
|
|
16
|
+
loadEnv(mergeOpts(cfg, rest));
|
|
30
17
|
return process.env;
|
|
31
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Per-field merge: caller-supplied option wins, falling through to the
|
|
21
|
+
* matching config field, falling through to whatever default `loadEnv`
|
|
22
|
+
* has built in. Fields the caller doesn't pass and the config doesn't
|
|
23
|
+
* set are simply omitted from the merged options object.
|
|
24
|
+
*/
|
|
25
|
+
function mergeOpts(config, user) {
|
|
26
|
+
const out = {};
|
|
27
|
+
if (user.envFiles !== void 0) out.envFiles = user.envFiles;
|
|
28
|
+
else if (config.envFiles !== void 0) out.envFiles = [...config.envFiles];
|
|
29
|
+
if (user.envPath !== void 0) out.envPath = user.envPath;
|
|
30
|
+
else if (config.envPath !== void 0) out.envPath = config.envPath;
|
|
31
|
+
if (user.cascade !== void 0) out.cascade = user.cascade;
|
|
32
|
+
else if (config.cascade !== void 0) out.cascade = config.cascade;
|
|
33
|
+
if (user.vault !== void 0) out.vault = user.vault;
|
|
34
|
+
if (user.variables !== void 0) out.variables = user.variables;
|
|
35
|
+
if (user.override !== void 0) out.override = user.override;
|
|
36
|
+
else if (config.override !== void 0) out.override = config.override;
|
|
37
|
+
if (user.quiet !== void 0) out.quiet = user.quiet;
|
|
38
|
+
else if (config.quiet !== void 0) out.quiet = config.quiet;
|
|
39
|
+
if (user.autoDetect !== void 0) out.autoDetect = user.autoDetect;
|
|
40
|
+
else if (config.autoDetect !== void 0) out.autoDetect = config.autoDetect;
|
|
41
|
+
if (user.nodeEnvMap !== void 0) out.nodeEnvMap = user.nodeEnvMap;
|
|
42
|
+
else if (config.nodeEnvMap !== void 0) out.nodeEnvMap = config.nodeEnvMap;
|
|
43
|
+
if (user.required !== void 0) out.required = user.required;
|
|
44
|
+
else if (config.required !== void 0) out.required = config.required;
|
|
45
|
+
if (user.expand !== void 0) out.expand = user.expand;
|
|
46
|
+
else if (config.expand !== void 0) out.expand = config.expand;
|
|
47
|
+
if (user.defaults !== void 0) out.defaults = user.defaults;
|
|
48
|
+
else if (config.defaults !== void 0) out.defaults = config.defaults;
|
|
49
|
+
if (user.workspaceRoot !== void 0) out.workspaceRoot = user.workspaceRoot;
|
|
50
|
+
else if (config.workspaceRoot !== void 0) out.workspaceRoot = config.workspaceRoot;
|
|
51
|
+
if (user.schema !== void 0) out.schema = user.schema;
|
|
52
|
+
else if (config.schema !== void 0) out.schema = config.schema;
|
|
53
|
+
if (user.resolvers !== void 0) out.resolvers = user.resolvers;
|
|
54
|
+
else if (config.resolvers !== void 0) out.resolvers = config.resolvers;
|
|
55
|
+
if (user.publicPrefixes !== void 0) out.publicPrefixes = user.publicPrefixes;
|
|
56
|
+
else if (config.publicPrefixes !== void 0) out.publicPrefixes = config.publicPrefixes;
|
|
57
|
+
if (user.publicSource !== void 0) out.publicSource = user.publicSource;
|
|
58
|
+
else if (config.publicSource !== void 0) out.publicSource = config.publicSource;
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
32
61
|
//#endregion
|
|
33
|
-
export { envx as default, envx };
|
|
62
|
+
export { BUILT_IN_PATTERNS, ENCRYPTED_PREFIX, auditFiles, decryptFiles, decryptValueAsymmetric, envx as default, envx, defaultKeysPath, detectEnvironment, encryptFiles, encryptValueAsymmetric, expandEnvSrc, expandRecord, findWorkspaceRoot, generateKeyPair, isEncrypted, loadDotenvxConfig, parseEnv, readKeysFile, resolveCwdOrWorkspace, resolveEnvPaths, rotateFiles, serializeEnv, toRecord, writeKeysFile };
|
|
34
63
|
|
|
35
64
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// #region -- Programmatic Entry Point ----------------------\n\nimport {\n loadEnv,\n loadDotenvxConfig,\n type LoadEnvOptions,\n} from \"@
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// #region -- Programmatic Entry Point ----------------------\n\nimport {\n loadEnv,\n loadDotenvxConfig,\n type DotenvxConfig,\n type LoadEnvOptions,\n} from \"@super-repo/envx-libs\";\n\n// Re-export the lib surface that consumers depend on (especially the\n// `auditFiles` API used by czar's `envxAuditPlugin`). Internal\n// packages `@super-repo/envx-libs` and `@super-repo/envx-common`\n// aren't on npm — `@super-repo/envx` is the only published entry, so\n// every public API has to be reachable from here.\nexport {\n // Audit (plaintext-secret scanner)\n auditFiles,\n BUILT_IN_PATTERNS,\n type AuditFinding,\n type AuditOptions,\n type SecretPattern,\n // Crypto primitives\n ENCRYPTED_PREFIX,\n generateKeyPair,\n encryptValueAsymmetric,\n decryptValueAsymmetric,\n isEncrypted,\n // High-level operations\n encryptFiles,\n decryptFiles,\n rotateFiles,\n // Env-file parser\n parseEnv,\n serializeEnv,\n toRecord,\n // Variable expansion\n expandRecord,\n expandEnvSrc,\n // Config + path resolution\n loadDotenvxConfig,\n findWorkspaceRoot,\n resolveCwdOrWorkspace,\n resolveEnvPaths,\n detectEnvironment,\n // Keys-file management\n readKeysFile,\n writeKeysFile,\n defaultKeysPath,\n} from \"@super-repo/envx-libs\";\n\nexport type {\n DotenvxConfig,\n LoadEnvOptions,\n ProcessedEnv,\n ProcessingError,\n ErrorCode,\n RunOptions,\n RunResult,\n ExpandOptions,\n ExpandResult,\n EnvLine,\n KvLine,\n RawLine,\n} from \"@super-repo/envx-libs\";\n\n/**\n * Load env files into `process.env` and return it.\n *\n * Modeled after the `dotenv` API but with explicit scoping. Three call\n * shapes:\n *\n * ```ts\n * import envx from \"@super-repo/envx\";\n *\n * envx(); // load `.env` (auto-detect from NODE_ENV / VERCEL_ENV / NETLIFY)\n * envx(\"prod\"); // load `.env` with cascade=prod (.env, .env.prod, .env.local, .env.prod.local)\n * envx({ envFiles: [\"vault/.env.prod\"], envPath: \"vault\", quiet: false }); // full options\n * ```\n *\n * In all cases `process.env` is mutated and returned for ergonomic\n * destructuring. Config file discovery (`envx.config.{ts,js,json}` /\n * `package.json` `envx.config`) runs automatically; programmatic\n * options layer on top of the discovered config, with caller-supplied\n * fields winning per-field.\n */\n/**\n * Programmatic options accept everything `LoadEnvOptions` does plus an\n * optional `profile` name. When set, the matching `config.profiles[name]`\n * is merged onto the base config (per-field) before the user options\n * take over.\n */\nexport type EnvxProgrammaticOptions = LoadEnvOptions & { readonly profile?: string };\n\nfunction envx(): NodeJS.ProcessEnv;\nfunction envx(scope: string): NodeJS.ProcessEnv;\nfunction envx(opts: EnvxProgrammaticOptions): NodeJS.ProcessEnv;\nfunction envx(arg?: string | EnvxProgrammaticOptions): NodeJS.ProcessEnv {\n const { config: baseConfig } = loadDotenvxConfig();\n const userOpts: EnvxProgrammaticOptions =\n arg === undefined\n ? {}\n : typeof arg === \"string\"\n ? { cascade: arg }\n : arg;\n let cfg: DotenvxConfig = baseConfig;\n if (userOpts.profile) {\n const profile = cfg.profiles?.[userOpts.profile];\n if (!profile) {\n throw new Error(\n `[ENVX_UNKNOWN_PROFILE] '${userOpts.profile}' not found (available: ${\n Object.keys(cfg.profiles ?? {}).join(\", \") || \"none\"\n })`,\n );\n }\n cfg = { ...cfg, ...profile };\n }\n // Strip `profile` before passing to mergeOpts — it's not a LoadEnvOptions field.\n const { profile: _drop, ...rest } = userOpts;\n void _drop;\n loadEnv(mergeOpts(cfg, rest));\n return process.env;\n}\n\n/**\n * Per-field merge: caller-supplied option wins, falling through to the\n * matching config field, falling through to whatever default `loadEnv`\n * has built in. Fields the caller doesn't pass and the config doesn't\n * set are simply omitted from the merged options object.\n */\nfunction mergeOpts(\n config: DotenvxConfig,\n user: LoadEnvOptions,\n): LoadEnvOptions {\n const out: { -readonly [K in keyof LoadEnvOptions]: LoadEnvOptions[K] } = {};\n\n // env-file selection\n if (user.envFiles !== undefined) out.envFiles = user.envFiles;\n else if (config.envFiles !== undefined) out.envFiles = [...config.envFiles];\n\n if (user.envPath !== undefined) out.envPath = user.envPath;\n else if (config.envPath !== undefined) out.envPath = config.envPath;\n\n if (user.cascade !== undefined) out.cascade = user.cascade;\n else if (config.cascade !== undefined) out.cascade = config.cascade;\n\n if (user.vault !== undefined) out.vault = user.vault;\n\n if (user.variables !== undefined) out.variables = user.variables;\n\n // load behavior\n if (user.override !== undefined) out.override = user.override;\n else if (config.override !== undefined) out.override = config.override;\n\n if (user.quiet !== undefined) out.quiet = user.quiet;\n else if (config.quiet !== undefined) out.quiet = config.quiet;\n\n // auto-detection\n if (user.autoDetect !== undefined) out.autoDetect = user.autoDetect;\n else if (config.autoDetect !== undefined) out.autoDetect = config.autoDetect;\n\n if (user.nodeEnvMap !== undefined) out.nodeEnvMap = user.nodeEnvMap;\n else if (config.nodeEnvMap !== undefined) out.nodeEnvMap = config.nodeEnvMap;\n\n // post-load behavior\n if (user.required !== undefined) out.required = user.required;\n else if (config.required !== undefined) out.required = config.required;\n\n if (user.expand !== undefined) out.expand = user.expand;\n else if (config.expand !== undefined) out.expand = config.expand;\n\n if (user.defaults !== undefined) out.defaults = user.defaults;\n else if (config.defaults !== undefined) out.defaults = config.defaults;\n\n // advanced\n if (user.workspaceRoot !== undefined) out.workspaceRoot = user.workspaceRoot;\n else if (config.workspaceRoot !== undefined) out.workspaceRoot = config.workspaceRoot;\n\n if (user.schema !== undefined) out.schema = user.schema;\n else if (config.schema !== undefined) out.schema = config.schema;\n\n if (user.resolvers !== undefined) out.resolvers = user.resolvers;\n else if (config.resolvers !== undefined) out.resolvers = config.resolvers;\n\n if (user.publicPrefixes !== undefined) out.publicPrefixes = user.publicPrefixes;\n else if (config.publicPrefixes !== undefined) out.publicPrefixes = config.publicPrefixes;\n\n if (user.publicSource !== undefined) out.publicSource = user.publicSource;\n else if (config.publicSource !== undefined) out.publicSource = config.publicSource;\n\n return out;\n}\n\nexport default envx;\nexport { envx };\n\n// Re-export the types callers will need to type their own wrappers.\nexport type { LoadEnvOptions };\n\n// #endregion -----------------------------------------------\n"],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Auto-detection
|
|
2
|
+
|
|
3
|
+
When `--env` is left at its default (`[".env"]`) and no `envFiles` is set in config, envx auto-detects the deployment environment from well-known platform signals and rewrites the env-file suffix accordingly. This page walks through every input shape with the resulting file load.
|
|
4
|
+
|
|
5
|
+
The implementation is `detectEnvironment()` — bundled into this package's dist via Vite.
|
|
6
|
+
|
|
7
|
+
## Precedence
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
1. VERCEL → detection from VERCEL_ENV
|
|
11
|
+
2. NETLIFY → detection from CONTEXT
|
|
12
|
+
3. NODE_ENV → built-in map, then lowercased passthrough
|
|
13
|
+
4. (none) → .env (no rewrite)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The first signal that fires wins. So a Vercel build with `NODE_ENV=development` set to a non-default still reads `VERCEL_ENV` first.
|
|
17
|
+
|
|
18
|
+
## Worked examples
|
|
19
|
+
|
|
20
|
+
### No platform signals (typical local dev)
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
unset VERCEL VERCEL_ENV NETLIFY CONTEXT NODE_ENV
|
|
24
|
+
envx -- node app.js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| signal | value |
|
|
28
|
+
|------------|-----------|
|
|
29
|
+
| (none) | — |
|
|
30
|
+
|
|
31
|
+
→ Detected: **`root`** → loads **`.env`**.
|
|
32
|
+
|
|
33
|
+
### Local dev with NODE_ENV
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
NODE_ENV=development envx -- node app.js
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| signal | value |
|
|
40
|
+
|-------------|---------------|
|
|
41
|
+
| `NODE_ENV` | `development` |
|
|
42
|
+
|
|
43
|
+
→ Built-in map matches `development → "dev"` → loads **`.env.dev`**.
|
|
44
|
+
|
|
45
|
+
### Staging environment via NODE_ENV (no config required)
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
NODE_ENV=staging envx -- pnpm test
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| signal | value |
|
|
52
|
+
|-------------|-----------|
|
|
53
|
+
| `NODE_ENV` | `staging` |
|
|
54
|
+
|
|
55
|
+
`staging` isn't in the built-in map, so envx **passes it through lowercased**. → Loads **`.env.staging`**.
|
|
56
|
+
|
|
57
|
+
The same rule applies to any value: `NODE_ENV=qa` → `.env.qa`, `NODE_ENV=preview` → `.env.preview`, `NODE_ENV=PROD` → `.env.prod` (lowercased — built-in map normalizes the canonical names anyway).
|
|
58
|
+
|
|
59
|
+
### Vercel preview build
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# Set by Vercel automatically:
|
|
63
|
+
VERCEL=1
|
|
64
|
+
VERCEL_ENV=preview
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| signal | value |
|
|
68
|
+
|--------------|-----------|
|
|
69
|
+
| `VERCEL` | `1` |
|
|
70
|
+
| `VERCEL_ENV` | `preview` |
|
|
71
|
+
|
|
72
|
+
→ Detected: **`dev`** → loads **`.env.dev`**.
|
|
73
|
+
|
|
74
|
+
`VERCEL_ENV=development` and `VERCEL_ENV=preview` both map to `dev`. Only `VERCEL_ENV=production` maps to `prod`.
|
|
75
|
+
|
|
76
|
+
### Vercel production build
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
VERCEL=1
|
|
80
|
+
VERCEL_ENV=production
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
→ Detected: **`prod`** → loads **`.env.prod`**.
|
|
84
|
+
|
|
85
|
+
### Netlify production build
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
NETLIFY=true
|
|
89
|
+
CONTEXT=production
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
→ Detected: **`prod`** → loads **`.env.prod`**.
|
|
93
|
+
|
|
94
|
+
### Netlify preview / branch deploy
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
NETLIFY=true
|
|
98
|
+
CONTEXT=deploy-preview
|
|
99
|
+
# or: CONTEXT=branch-deploy
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
→ Detected: **`dev`** → loads **`.env.dev`**.
|
|
103
|
+
|
|
104
|
+
Any other Netlify `CONTEXT` value also resolves to `dev` (Netlify uses `production` only for the canonical site).
|
|
105
|
+
|
|
106
|
+
### NODE_ENV with custom mapping
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// envx.config.ts
|
|
110
|
+
export default {
|
|
111
|
+
nodeEnvMap: {
|
|
112
|
+
production: "prod",
|
|
113
|
+
development: "dev",
|
|
114
|
+
local: "local",
|
|
115
|
+
// your additions:
|
|
116
|
+
qa: "qa-prod", // NODE_ENV=qa loads .env.qa-prod, not .env.qa
|
|
117
|
+
preview: "", // NODE_ENV=preview loads .env (no suffix)
|
|
118
|
+
"2025-prod": "prod", // alias an arbitrary value
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
NODE_ENV=qa envx -- node app.js
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
→ Map override hits `qa → "qa-prod"` → loads **`.env.qa-prod`**.
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
NODE_ENV=preview envx -- node app.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
→ Map override hits `preview → ""` (empty string = no suffix) → loads **`.env`**.
|
|
134
|
+
|
|
135
|
+
### Disabling auto-detection entirely
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// envx.config.ts
|
|
139
|
+
export default { autoDetect: false }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
NODE_ENV=production envx -- node app.js
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
→ Auto-detection is off → loads **`.env`** regardless of platform signals. Pass `--env` explicitly when you want a different file.
|
|
147
|
+
|
|
148
|
+
## Verifying what envx will do
|
|
149
|
+
|
|
150
|
+
`envx info` prints the resolved settings, the platform signals it sees, the detected environment, and the active `NODE_ENV → suffix` map. Use it as a sanity check before deploying.
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
envx info
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Sample output:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
envx info
|
|
160
|
+
────────────────────────────────────────────────────────────
|
|
161
|
+
Workspace
|
|
162
|
+
cwd /repo/apps/web
|
|
163
|
+
workspace root /repo
|
|
164
|
+
|
|
165
|
+
Config
|
|
166
|
+
source /repo/envx.config.ts
|
|
167
|
+
origin auto
|
|
168
|
+
|
|
169
|
+
Resolved settings
|
|
170
|
+
envFiles [".env"]
|
|
171
|
+
envPath (none)
|
|
172
|
+
cascade (off)
|
|
173
|
+
envKeysFile /repo/.env.keys (default: cwd-first, ws-root fallback)
|
|
174
|
+
override false
|
|
175
|
+
quiet true
|
|
176
|
+
autoDetect true
|
|
177
|
+
|
|
178
|
+
Platform signals (process.env)
|
|
179
|
+
VERCEL (unset)
|
|
180
|
+
VERCEL_ENV (unset)
|
|
181
|
+
NETLIFY (unset)
|
|
182
|
+
CONTEXT (unset)
|
|
183
|
+
NODE_ENV staging
|
|
184
|
+
|
|
185
|
+
Auto-detection
|
|
186
|
+
source NODE_ENV
|
|
187
|
+
detected staging
|
|
188
|
+
→ env file .env.staging
|
|
189
|
+
|
|
190
|
+
NODE_ENV → suffix mapping
|
|
191
|
+
development → .env.dev (default)
|
|
192
|
+
local → .env.local (default)
|
|
193
|
+
production → .env.prod (default)
|
|
194
|
+
qa → .env.qa-prod (config)
|
|
195
|
+
preview → (no suffix → .env) (config)
|
|
196
|
+
|
|
197
|
+
Resolved env file paths (would be loaded in this order)
|
|
198
|
+
.env.staging → /repo/.env.staging
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Summary table
|
|
202
|
+
|
|
203
|
+
| input | detected | file |
|
|
204
|
+
|------------------------------------------------|------------|------------|
|
|
205
|
+
| nothing | `root` | `.env` |
|
|
206
|
+
| `NODE_ENV=production` | `prod` | `.env.prod`|
|
|
207
|
+
| `NODE_ENV=development` | `dev` | `.env.dev` |
|
|
208
|
+
| `NODE_ENV=local` | `local` | `.env.local`|
|
|
209
|
+
| `NODE_ENV=staging` | `staging` | `.env.staging`|
|
|
210
|
+
| `NODE_ENV=QA` (uppercased) | `qa` | `.env.qa` |
|
|
211
|
+
| `VERCEL=1, VERCEL_ENV=production` | `prod` | `.env.prod`|
|
|
212
|
+
| `VERCEL=1, VERCEL_ENV=preview` | `dev` | `.env.dev` |
|
|
213
|
+
| `VERCEL=1` (no `VERCEL_ENV`) | `dev` | `.env.dev` |
|
|
214
|
+
| `NETLIFY=true, CONTEXT=production` | `prod` | `.env.prod`|
|
|
215
|
+
| `NETLIFY=true, CONTEXT=deploy-preview` | `dev` | `.env.dev` |
|
|
216
|
+
| `NETLIFY=true, CONTEXT=branch-deploy` | `dev` | `.env.dev` |
|
|
217
|
+
| `autoDetect: false` (any signals) | `root` | `.env` |
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
envx settings come from four layers, resolved per-field with CLI flags winning. This page documents the schema, the discovery walk, and the merge rules.
|
|
4
|
+
|
|
5
|
+
The full schema is the `DotenvxConfig` interface — bundled into this package's dist and importable from the CLI's bundled types.
|
|
6
|
+
|
|
7
|
+
## The schema
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// envx.config.ts
|
|
11
|
+
export default {
|
|
12
|
+
// ── env-file selection ─────────────────────────────────
|
|
13
|
+
envFiles: [".env", ".env.local"], // bypass auto-detect; explicit list
|
|
14
|
+
envPath: "vault", // workspace-relative subdir (cwd-first, ws-root fallback)
|
|
15
|
+
cascade: true, // boolean: cascade using the auto-detected env name
|
|
16
|
+
// (CLI `--cascade <name>` overrides with an explicit string)
|
|
17
|
+
variables: ["DEBUG=1"], // KEY=VALUE overrides applied after load
|
|
18
|
+
|
|
19
|
+
// ── encryption keys ────────────────────────────────────
|
|
20
|
+
envKeysFile: ".env.keys", // overrides the cwd-first / workspace fallback default
|
|
21
|
+
|
|
22
|
+
// ── load behavior ──────────────────────────────────────
|
|
23
|
+
override: false, // false: existing process.env wins
|
|
24
|
+
// true: env files win (incompatible with cascade)
|
|
25
|
+
quiet: true, // suppress dotenv's load-line output
|
|
26
|
+
|
|
27
|
+
// ── auto-detection ─────────────────────────────────────
|
|
28
|
+
autoDetect: true, // false disables Vercel/Netlify/NODE_ENV detection
|
|
29
|
+
nodeEnvMap: { // override the NODE_ENV → suffix mapping
|
|
30
|
+
production: "prod", // (built-ins shown — yours layer on top)
|
|
31
|
+
development: "dev",
|
|
32
|
+
local: "local",
|
|
33
|
+
qa: "qa-prod", // NODE_ENV=qa → .env.qa-prod
|
|
34
|
+
preview: "", // NODE_ENV=preview → .env (no suffix)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// ── post-load behavior ─────────────────────────────────
|
|
38
|
+
required: ["DATABASE_URL", "API_KEY"], // fail-fast: exit 1 if any are unset after load
|
|
39
|
+
expand: true, // auto-resolve ${VAR} references against process.env
|
|
40
|
+
defaults: { // applied AFTER files + variables, only for unset keys
|
|
41
|
+
LOG_LEVEL: "info",
|
|
42
|
+
PORT: "3000",
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// ── advanced ───────────────────────────────────────────
|
|
46
|
+
workspaceRoot: "/abs/path/to/repo", // escape hatch — skip findWorkspaceRoot() walk-up
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
All fields are optional. Anything you omit falls through to the built-in default for that field — see [auto-detection](./auto-detection.md) for the per-field defaults.
|
|
51
|
+
|
|
52
|
+
### Field reference
|
|
53
|
+
|
|
54
|
+
| field | type | default | meaning |
|
|
55
|
+
|----------------|-------------------------------|--------------------------------|---------|
|
|
56
|
+
| `envFiles` | `string[]` | `[".env"]` (with auto-detect) | Files to load. Bare names get the `.env.` prefix. Bypasses auto-detection. |
|
|
57
|
+
| `envPath` | `string` | _(none)_ | Subdirectory to look in. Cwd-first; falls back to `<workspaceRoot>/<envPath>`. |
|
|
58
|
+
| `cascade` | `boolean` | `false` | When `true`, expand each base file into `.env.<env>.local`, `.env.local`, `.env.<env>`, `.env` — where `<env>` is the auto-detected name. CLI `--cascade <name>` overrides with an explicit string. |
|
|
59
|
+
| `variables` | `string[]` | `[]` | `KEY=VALUE` overrides applied after env files load. |
|
|
60
|
+
| `envKeysFile` | `string` | `.env.keys` (cwd-first walk-up)| Path to the keys file. Cwd-first with workspace-root fallback. |
|
|
61
|
+
| `override` | `boolean` | `false` | When `true`, env file values overwrite existing `process.env`. Conflicts with `cascade`. |
|
|
62
|
+
| `quiet` | `boolean` | `true` | Suppress dotenv's `Loading environment from:` lines. |
|
|
63
|
+
| `autoDetect` | `boolean` | `true` | Toggle Vercel/Netlify/NODE_ENV detection. |
|
|
64
|
+
| `nodeEnvMap` | `Record<string, string>` | see below | Override the `NODE_ENV → suffix` map. |
|
|
65
|
+
| `required` | `string[]` | `[]` | Keys that MUST be set in `process.env` after envx finishes. Missing values cause `process.exit(1)`. |
|
|
66
|
+
| `expand` | `boolean` | `false` | Auto-resolve `${VAR}` / `$VAR` references against `process.env` after files + variables load. |
|
|
67
|
+
| `defaults` | `Record<string, string>` | `{}` | Fallback values applied only to keys still unset after files + variables. Different from `variables`, which always overrides. |
|
|
68
|
+
| `workspaceRoot`| `string` | _(auto-detect)_ | Explicit workspace root, skipping `findWorkspaceRoot()` walk-up. Useful for non-standard layouts. |
|
|
69
|
+
|
|
70
|
+
### Built-in `nodeEnvMap`
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
{
|
|
74
|
+
production: "prod",
|
|
75
|
+
development: "dev",
|
|
76
|
+
local: "local",
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Anything **not** in the map (after merging your overrides) passes through as the lowercased `NODE_ENV` value — `staging` → `.env.staging`, `qa` → `.env.qa`. Use empty string `""` to force "no suffix" for a specific value (`preview: ""` → `NODE_ENV=preview` loads `.env`).
|
|
81
|
+
|
|
82
|
+
## Discovery walk
|
|
83
|
+
|
|
84
|
+
envx finds **one** config file per invocation (no merging across files) in this exact order:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
1. --config <path> (CLI only — wins if present)
|
|
88
|
+
2. package.json#envx.config: "<path>" (walks UP from cwd to /)
|
|
89
|
+
3. envx.config.{ts,mts,js,mjs,cjs,json} (cwd ONLY — does not walk up)
|
|
90
|
+
4. (none) (built-in defaults)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The first match wins; we don't merge multiple files together. This means:
|
|
94
|
+
|
|
95
|
+
- A `package.json#envx.config` reference anywhere from cwd up to `/` will be picked up. Use this to point sub-packages at a workspace-root config: `{ "envx": { "config": "../../envx.config.ts" } }`.
|
|
96
|
+
- A bare `envx.config.ts` at the workspace root is **only** found if `cwd` happens to be the workspace root itself. Sub-packages don't auto-discover it — they need a `package.json#envx.config` reference.
|
|
97
|
+
|
|
98
|
+
`package.json#envx.config` only accepts a string path (not an inline object). This keeps the manifest free of secret-related fields and forces config to be a single file you can lint and review.
|
|
99
|
+
|
|
100
|
+
### Programmatic API uses the same discovery
|
|
101
|
+
|
|
102
|
+
`envx()` and `envx({...})` from `@super-repo/envx` run the same `loadDotenvxConfig()` discovery as the CLI. The only difference is step 1 (`--config`) is unavailable; programmatic callers pass options directly to `envx({...})`. Programmatic args play the same role as CLI flags — see [Per-field merge](#per-field-merge) below.
|
|
103
|
+
|
|
104
|
+
### Example: per-package overrides
|
|
105
|
+
|
|
106
|
+
Because `envx.config.*` is **cwd-only**, sub-packages need a `package.json#envx.config` reference to find a workspace-root config. The minimal monorepo layout looks like:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
/repo
|
|
110
|
+
├── envx.config.ts ← workspace defaults
|
|
111
|
+
├── packages/
|
|
112
|
+
│ ├── api/
|
|
113
|
+
│ │ └── package.json ← { "envx": { "config": "../../envx.config.ts" } }
|
|
114
|
+
│ └── docker-builder/
|
|
115
|
+
│ ├── envx.config.ts ← package-specific overrides
|
|
116
|
+
│ └── package.json ← (no envx.config field needed — local file is in cwd)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Running `envx -- node app.js` from `/repo/packages/api`:
|
|
120
|
+
- Discovery step 2 walks up `packages/api/package.json` → `packages/package.json` → `/repo/package.json`. The first one is read first; its `envx.config` field points at `../../envx.config.ts`, which resolves to `/repo/envx.config.ts`. **Match — that file is loaded.**
|
|
121
|
+
|
|
122
|
+
Running the same command from `/repo/packages/docker-builder`:
|
|
123
|
+
- Discovery step 2 walks up the package.jsons. None has an `envx.config` field. No match.
|
|
124
|
+
- Discovery step 3 looks for `envx.config.*` in cwd (`/repo/packages/docker-builder/`). **Match — that file is loaded.**
|
|
125
|
+
|
|
126
|
+
Configs don't merge across the walk — the first one found is the **only** one read. If you want true workspace-root + package-level layering, that's a feature we don't currently have.
|
|
127
|
+
|
|
128
|
+
## Per-field merge
|
|
129
|
+
|
|
130
|
+
Within a single invocation, each setting is resolved **independently** through these layers:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
For each setting field (envFiles, cascade, envPath, envKeysFile,
|
|
134
|
+
override, quiet, autoDetect, nodeEnvMap, …):
|
|
135
|
+
|
|
136
|
+
1. Did the CLI flag — or programmatic arg — set this field? → use that, stop
|
|
137
|
+
2. Is it set in the loaded config? → use that, stop
|
|
138
|
+
3. Otherwise → use the built-in default
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
So you can mix layers freely:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
# Config provides envFiles + nodeEnvMap; CLI overrides only `cascade`.
|
|
145
|
+
envx --cascade prod -- node app.js
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// envx.config.ts
|
|
150
|
+
export default {
|
|
151
|
+
envFiles: [".env", "vault/.env.prod"],
|
|
152
|
+
nodeEnvMap: { qa: "qa-prod" },
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Effective settings:
|
|
157
|
+
|
|
158
|
+
| field | value | source |
|
|
159
|
+
|-------------|------------------------------------|------------------|
|
|
160
|
+
| `envFiles` | `[".env", "vault/.env.prod"]` | config |
|
|
161
|
+
| `cascade` | `"prod"` (string overrides config's boolean) | CLI flag |
|
|
162
|
+
| `envPath` | _(none)_ | default |
|
|
163
|
+
| `nodeEnvMap`| `{ qa: "qa-prod", + built-ins }` | config (merged) |
|
|
164
|
+
| `override` | `false` | default |
|
|
165
|
+
| `quiet` | `true` | default |
|
|
166
|
+
| `autoDetect`| `true` | default |
|
|
167
|
+
|
|
168
|
+
## Load pipeline
|
|
169
|
+
|
|
170
|
+
When `envx run` (or any subcommand that calls `loadEnv`) executes, the steps run in this exact order:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
1. resolve workspaceRoot (config field, else findWorkspaceRoot())
|
|
174
|
+
2. resolve env file paths (envFiles → .env. prefix, cascade expansion)
|
|
175
|
+
3. load each env file (encrypted values decrypted with .env.keys)
|
|
176
|
+
→ mutates process.env (subject to `override`)
|
|
177
|
+
4. apply `variables` (-v KEY=VALUE on CLI / variables in config)
|
|
178
|
+
→ unconditional overwrite of process.env
|
|
179
|
+
5. apply `defaults` (only for keys still undefined)
|
|
180
|
+
→ fills holes; never overwrites
|
|
181
|
+
6. expand `${VAR}` references (when `expand: true`)
|
|
182
|
+
→ in-place substitution against process.env
|
|
183
|
+
7. enforce `required` (any unset → log + process.exit(1))
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Implications:
|
|
187
|
+
- `variables` beats `defaults` — variables always overwrite, defaults only fill in.
|
|
188
|
+
- `expand` runs AFTER `defaults`, so a default like `URL: "${HOST}/api"` can resolve.
|
|
189
|
+
- `required` runs LAST, so it sees the final shape of `process.env`. A key that was unset in the env file but provided by `defaults` will pass the `required` check.
|
|
190
|
+
|
|
191
|
+
## Variable-value precedence (after files load)
|
|
192
|
+
|
|
193
|
+
This is separate from setting precedence. Once envx has resolved settings and loaded the env files, the actual values that end up in `process.env` follow this order:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Final value of process.env.SOME_KEY is the FIRST hit, top-down:
|
|
197
|
+
|
|
198
|
+
1. `-v SOME_KEY=…` passed on the CLI (always wins)
|
|
199
|
+
2. Existing process.env.SOME_KEY when override=false (the default)
|
|
200
|
+
OR, when override=true:
|
|
201
|
+
The most-specific env file's value (cascade order)
|
|
202
|
+
3. The next-most-specific env file's value
|
|
203
|
+
4. … all the way down the cascade
|
|
204
|
+
5. unset
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`-v` is **not** a config layer — it's a post-load mutation of `process.env`. Use it for one-off injections; use `envFiles` / `cascade` to swap whole environments.
|
|
208
|
+
|
|
209
|
+
## Validation
|
|
210
|
+
|
|
211
|
+
The config is parsed by `normalize()` in `config.ts`. Unknown fields are silently dropped (so adding a typo won't error — it'll just have no effect). Wrong types for known fields are dropped too:
|
|
212
|
+
|
|
213
|
+
| input | result |
|
|
214
|
+
|----------------------------------------|----------------------------------------------|
|
|
215
|
+
| `envFiles: ".env"` (string, not array) | dropped — must be `string[]` |
|
|
216
|
+
| `nodeEnvMap: { qa: 42 }` | the entry is dropped (value must be string) |
|
|
217
|
+
| `unknown: "field"` | silently dropped |
|
|
218
|
+
|
|
219
|
+
If you want strict validation in CI, run `envx info` and grep for the field — the displayed value tells you whether envx accepted it. (A future addition could be a `--strict` flag that errors on unknowns; not currently implemented.)
|
|
220
|
+
|
|
221
|
+
## See also
|
|
222
|
+
|
|
223
|
+
- [Auto-detection](./auto-detection.md) — every signal shape walked through with examples.
|
|
224
|
+
- [Recipes](./recipes.md) — common monorepo layouts with the exact config files.
|