@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.
Files changed (42) hide show
  1. package/README.md +970 -104
  2. package/dist/auto.js.map +1 -1
  3. package/dist/chunks/commands-D3eQPQO6.js +1502 -0
  4. package/dist/chunks/commands-D3eQPQO6.js.map +1 -0
  5. package/dist/chunks/{src-CDuEfaCY.js → src-D0n2wHDg.js} +0 -0
  6. package/dist/chunks/src-D0n2wHDg.js.map +1 -0
  7. package/dist/cli.js +1 -1
  8. package/dist/commands/audit.d.ts +13 -0
  9. package/dist/commands/audit.d.ts.map +1 -0
  10. package/dist/commands/bake.d.ts +18 -0
  11. package/dist/commands/bake.d.ts.map +1 -0
  12. package/dist/commands/diff.d.ts +16 -0
  13. package/dist/commands/diff.d.ts.map +1 -0
  14. package/dist/commands/doctor.d.ts +16 -0
  15. package/dist/commands/doctor.d.ts.map +1 -0
  16. package/dist/commands/encrypt.d.ts.map +1 -1
  17. package/dist/commands/hook.d.ts +18 -0
  18. package/dist/commands/hook.d.ts.map +1 -0
  19. package/dist/commands/index.d.ts.map +1 -1
  20. package/dist/commands/index.js +1 -1
  21. package/dist/commands/info.d.ts +10 -0
  22. package/dist/commands/info.d.ts.map +1 -0
  23. package/dist/commands/rotate.d.ts +13 -0
  24. package/dist/commands/rotate.d.ts.map +1 -0
  25. package/dist/commands/run.d.ts.map +1 -1
  26. package/dist/commands/template.d.ts +13 -0
  27. package/dist/commands/template.d.ts.map +1 -0
  28. package/dist/commands/types.d.ts +14 -0
  29. package/dist/commands/types.d.ts.map +1 -0
  30. package/dist/commands/watch.d.ts +14 -0
  31. package/dist/commands/watch.d.ts.map +1 -0
  32. package/dist/index.d.ts +16 -4
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +57 -28
  35. package/dist/index.js.map +1 -1
  36. package/docs/auto-detection.md +217 -0
  37. package/docs/configuration.md +224 -0
  38. package/docs/recipes.md +234 -0
  39. package/package.json +6 -4
  40. package/dist/chunks/commands-B8vc6UKO.js +0 -354
  41. package/dist/chunks/commands-B8vc6UKO.js.map +0 -1
  42. package/dist/chunks/src-CDuEfaCY.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,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,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC;AA0DvD,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,YAAY,EAAE,cAAc,EAAE,CAAC"}
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 loadDotenvxConfig, i as loadEnv } from "./chunks/src-CDuEfaCY.js";
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
- let opts;
6
- if (arg === void 0) opts = {
7
- envFiles: config.envFiles ? [...config.envFiles] : [".env"],
8
- ...config.cascade !== void 0 ? { cascade: config.cascade } : {},
9
- ...config.envPath !== void 0 ? { envPath: config.envPath } : {},
10
- ...config.override !== void 0 ? { override: config.override } : {},
11
- ...config.quiet !== void 0 ? { quiet: config.quiet } : {}
12
- };
13
- else if (typeof arg === "string") opts = {
14
- envFiles: config.envFiles ? [...config.envFiles] : [".env"],
15
- cascade: arg,
16
- ...config.envPath !== void 0 ? { envPath: config.envPath } : {},
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 \"@honeycluster/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 \"@honeycluster/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; CLI-level options\n * still win when both are passed.\n */\nfunction envx(): NodeJS.ProcessEnv;\nfunction envx(scope: string): NodeJS.ProcessEnv;\nfunction envx(opts: LoadEnvOptions): NodeJS.ProcessEnv;\nfunction envx(arg?: string | LoadEnvOptions): NodeJS.ProcessEnv {\n // Resolve config-file defaults first; CLI/programmatic args override.\n const { config } = loadDotenvxConfig();\n\n let opts: LoadEnvOptions;\n if (arg === undefined) {\n opts = {\n envFiles: config.envFiles ? [...config.envFiles] : [\".env\"],\n ...(config.cascade !== undefined ? { cascade: config.cascade } : {}),\n ...(config.envPath !== undefined ? { envPath: config.envPath } : {}),\n ...(config.override !== undefined ? { override: config.override } : {}),\n ...(config.quiet !== undefined ? { quiet: config.quiet } : {}),\n };\n } else if (typeof arg === \"string\") {\n // String form is a cascade scope shortcut: envx(\"prod\") loads\n // .env, .env.prod, .env.local, .env.prod.local in that order.\n opts = {\n envFiles: config.envFiles ? [...config.envFiles] : [\".env\"],\n cascade: arg,\n ...(config.envPath !== undefined ? { envPath: config.envPath } : {}),\n ...(config.override !== undefined ? { override: config.override } : {}),\n ...(config.quiet !== undefined ? { quiet: config.quiet } : {}),\n };\n } else {\n // Object form: caller's options layered over config defaults.\n opts = {\n envFiles:\n arg.envFiles ?? (config.envFiles ? [...config.envFiles] : [\".env\"]),\n ...(arg.cascade !== undefined\n ? { cascade: arg.cascade }\n : config.cascade !== undefined\n ? { cascade: config.cascade }\n : {}),\n ...(arg.envPath !== undefined\n ? { envPath: arg.envPath }\n : config.envPath !== undefined\n ? { envPath: config.envPath }\n : {}),\n ...(arg.vault !== undefined ? { vault: arg.vault } : {}),\n ...(arg.variables !== undefined ? { variables: arg.variables } : {}),\n ...(arg.override !== undefined\n ? { override: arg.override }\n : config.override !== undefined\n ? { override: config.override }\n : {}),\n ...(arg.quiet !== undefined\n ? { quiet: arg.quiet }\n : config.quiet !== undefined\n ? { quiet: config.quiet }\n : {}),\n };\n }\n\n loadEnv(opts);\n return process.env;\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":";;AA8BA,SAAS,KAAK,KAAkD;CAE9D,MAAM,EAAE,WAAW,mBAAmB;CAEtC,IAAI;CACJ,IAAI,QAAQ,KAAA,GACV,OAAO;EACL,UAAU,OAAO,WAAW,CAAC,GAAG,OAAO,SAAS,GAAG,CAAC,OAAO;EAC3D,GAAI,OAAO,YAAY,KAAA,IAAY,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE;EACnE,GAAI,OAAO,YAAY,KAAA,IAAY,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE;EACnE,GAAI,OAAO,aAAa,KAAA,IAAY,EAAE,UAAU,OAAO,UAAU,GAAG,EAAE;EACtE,GAAI,OAAO,UAAU,KAAA,IAAY,EAAE,OAAO,OAAO,OAAO,GAAG,EAAE;EAC9D;MACI,IAAI,OAAO,QAAQ,UAGxB,OAAO;EACL,UAAU,OAAO,WAAW,CAAC,GAAG,OAAO,SAAS,GAAG,CAAC,OAAO;EAC3D,SAAS;EACT,GAAI,OAAO,YAAY,KAAA,IAAY,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE;EACnE,GAAI,OAAO,aAAa,KAAA,IAAY,EAAE,UAAU,OAAO,UAAU,GAAG,EAAE;EACtE,GAAI,OAAO,UAAU,KAAA,IAAY,EAAE,OAAO,OAAO,OAAO,GAAG,EAAE;EAC9D;MAGD,OAAO;EACL,UACE,IAAI,aAAa,OAAO,WAAW,CAAC,GAAG,OAAO,SAAS,GAAG,CAAC,OAAO;EACpE,GAAI,IAAI,YAAY,KAAA,IAChB,EAAE,SAAS,IAAI,SAAS,GACxB,OAAO,YAAY,KAAA,IACjB,EAAE,SAAS,OAAO,SAAS,GAC3B,EAAE;EACR,GAAI,IAAI,YAAY,KAAA,IAChB,EAAE,SAAS,IAAI,SAAS,GACxB,OAAO,YAAY,KAAA,IACjB,EAAE,SAAS,OAAO,SAAS,GAC3B,EAAE;EACR,GAAI,IAAI,UAAU,KAAA,IAAY,EAAE,OAAO,IAAI,OAAO,GAAG,EAAE;EACvD,GAAI,IAAI,cAAc,KAAA,IAAY,EAAE,WAAW,IAAI,WAAW,GAAG,EAAE;EACnE,GAAI,IAAI,aAAa,KAAA,IACjB,EAAE,UAAU,IAAI,UAAU,GAC1B,OAAO,aAAa,KAAA,IAClB,EAAE,UAAU,OAAO,UAAU,GAC7B,EAAE;EACR,GAAI,IAAI,UAAU,KAAA,IACd,EAAE,OAAO,IAAI,OAAO,GACpB,OAAO,UAAU,KAAA,IACf,EAAE,OAAO,OAAO,OAAO,GACvB,EAAE;EACT;CAGH,QAAQ,KAAK;CACb,OAAO,QAAQ"}
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.