@karmaniverous/get-dotenv 6.0.0-1 → 6.0.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.
Files changed (62) hide show
  1. package/README.md +91 -379
  2. package/dist/cli.d.ts +569 -0
  3. package/dist/cli.mjs +18788 -0
  4. package/dist/cliHost.d.ts +515 -184
  5. package/dist/cliHost.mjs +1931 -1428
  6. package/dist/config.d.ts +187 -14
  7. package/dist/config.mjs +256 -81
  8. package/dist/env-overlay.d.ts +212 -16
  9. package/dist/env-overlay.mjs +168 -4
  10. package/dist/getdotenv.cli.mjs +18227 -3487
  11. package/dist/index.d.ts +541 -260
  12. package/dist/index.mjs +18294 -3556
  13. package/dist/plugins-aws.d.ts +221 -91
  14. package/dist/plugins-aws.mjs +2360 -361
  15. package/dist/plugins-batch.d.ts +300 -103
  16. package/dist/plugins-batch.mjs +2519 -484
  17. package/dist/plugins-cmd.d.ts +229 -106
  18. package/dist/plugins-cmd.mjs +2480 -793
  19. package/dist/plugins-init.d.ts +221 -95
  20. package/dist/plugins-init.mjs +2102 -104
  21. package/dist/plugins.d.ts +246 -125
  22. package/dist/plugins.mjs +17861 -1973
  23. package/dist/templates/cli/index.ts +26 -0
  24. package/{templates/cli/ts → dist/templates/cli}/plugins/hello.ts +11 -6
  25. package/dist/templates/config/js/getdotenv.config.js +20 -0
  26. package/dist/templates/config/json/local/getdotenv.config.local.json +7 -0
  27. package/dist/templates/config/json/public/getdotenv.config.json +9 -0
  28. package/dist/templates/config/public/getdotenv.config.json +8 -0
  29. package/dist/templates/config/ts/getdotenv.config.ts +28 -0
  30. package/dist/templates/config/yaml/local/getdotenv.config.local.yaml +7 -0
  31. package/dist/templates/config/yaml/public/getdotenv.config.yaml +7 -0
  32. package/dist/templates/getdotenv.config.js +20 -0
  33. package/dist/templates/getdotenv.config.json +9 -0
  34. package/dist/templates/getdotenv.config.local.json +7 -0
  35. package/dist/templates/getdotenv.config.local.yaml +7 -0
  36. package/dist/templates/getdotenv.config.ts +28 -0
  37. package/dist/templates/getdotenv.config.yaml +7 -0
  38. package/dist/templates/hello.ts +43 -0
  39. package/dist/templates/index.ts +26 -0
  40. package/dist/templates/js/getdotenv.config.js +20 -0
  41. package/dist/templates/json/local/getdotenv.config.local.json +7 -0
  42. package/dist/templates/json/public/getdotenv.config.json +9 -0
  43. package/dist/templates/local/getdotenv.config.local.json +7 -0
  44. package/dist/templates/local/getdotenv.config.local.yaml +7 -0
  45. package/dist/templates/plugins/hello.ts +43 -0
  46. package/dist/templates/public/getdotenv.config.json +9 -0
  47. package/dist/templates/public/getdotenv.config.yaml +7 -0
  48. package/dist/templates/ts/getdotenv.config.ts +28 -0
  49. package/dist/templates/yaml/local/getdotenv.config.local.yaml +7 -0
  50. package/dist/templates/yaml/public/getdotenv.config.yaml +7 -0
  51. package/getdotenv.config.json +1 -19
  52. package/package.json +42 -39
  53. package/templates/cli/index.ts +26 -0
  54. package/templates/cli/plugins/hello.ts +43 -0
  55. package/templates/config/js/getdotenv.config.js +8 -3
  56. package/templates/config/json/public/getdotenv.config.json +0 -3
  57. package/templates/config/public/getdotenv.config.json +0 -5
  58. package/templates/config/ts/getdotenv.config.ts +9 -4
  59. package/templates/config/yaml/public/getdotenv.config.yaml +0 -3
  60. package/dist/plugins-demo.d.ts +0 -204
  61. package/dist/plugins-demo.mjs +0 -496
  62. package/templates/cli/ts/index.ts +0 -9
@@ -1,42 +1,238 @@
1
1
  /**
2
- * Scripts table shape (configurable shell type).
2
+ * Minimal root options shape shared by CLI and generator layers.
3
+ * Keep keys optional to respect exactOptionalPropertyTypes semantics.
4
+ *
5
+ * @public
6
+ */
7
+ interface RootOptionsShape {
8
+ /** Target environment (dotenv-expanded). */
9
+ env?: string;
10
+ /** Explicit variable overrides (dotenv-expanded). */
11
+ vars?: string;
12
+ /** Command to execute (dotenv-expanded). */
13
+ command?: string;
14
+ /** Output path for the consolidated environment file (dotenv-expanded). */
15
+ outputPath?: string;
16
+ /**
17
+ * Shell execution strategy.
18
+ * - `true`: use default OS shell.
19
+ * - `false`: use plain execution (no shell).
20
+ * - string: use specific shell path.
21
+ */
22
+ shell?: string | boolean;
23
+ /** Whether to load variables into `process.env`. */
24
+ loadProcess?: boolean;
25
+ /** Exclude all variables from loading. */
26
+ excludeAll?: boolean;
27
+ /** Exclude dynamic variables. */
28
+ excludeDynamic?: boolean;
29
+ /** Exclude environment-specific variables. */
30
+ excludeEnv?: boolean;
31
+ /** Exclude global variables. */
32
+ excludeGlobal?: boolean;
33
+ /** Exclude private variables. */
34
+ excludePrivate?: boolean;
35
+ /** Exclude public variables. */
36
+ excludePublic?: boolean;
37
+ /** Enable console logging of loaded variables. */
38
+ log?: boolean;
39
+ /** Enable debug logging to stderr. */
40
+ debug?: boolean;
41
+ /** Capture child process stdio (useful for tests/CI). */
42
+ capture?: boolean;
43
+ /** Fail on validation errors (schema/requiredKeys). */
44
+ strict?: boolean;
45
+ /** Enable presentation-time redaction of secret-like keys. */
46
+ redact?: boolean;
47
+ /** Enable entropy warnings for high-entropy values. */
48
+ warnEntropy?: boolean;
49
+ /** Entropy threshold (bits/char) for warnings (default 3.8). */
50
+ entropyThreshold?: number;
51
+ /** Minimum string length to check for entropy (default 16). */
52
+ entropyMinLength?: number;
53
+ /** Regex patterns for keys to exclude from entropy checks. */
54
+ entropyWhitelist?: ReadonlyArray<string>;
55
+ /** Additional regex patterns for keys to redact. */
56
+ redactPatterns?: string[];
57
+ /** Default target environment when not specified. */
58
+ defaultEnv?: string;
59
+ /** Token indicating a dotenv file (default: ".env"). */
60
+ dotenvToken?: string;
61
+ /** Path to dynamic variables module (default: undefined). */
62
+ dynamicPath?: string;
63
+ /**
64
+ * Emit diagnostics for child env composition.
65
+ * - `true`: trace all keys.
66
+ * - `string[]`: trace selected keys.
67
+ */
68
+ trace?: boolean | string[];
69
+ /** Paths to search for dotenv files (space-delimited string or array). */
70
+ paths?: string;
71
+ /** Delimiter for paths string (default: space). */
72
+ pathsDelimiter?: string;
73
+ /** Regex pattern for paths delimiter. */
74
+ pathsDelimiterPattern?: string;
75
+ /** Token indicating private variables (default: "local"). */
76
+ privateToken?: string;
77
+ /** Delimiter for vars string (default: space). */
78
+ varsDelimiter?: string;
79
+ /** Regex pattern for vars delimiter. */
80
+ varsDelimiterPattern?: string;
81
+ /** Assignment operator for vars (default: "="). */
82
+ varsAssignor?: string;
83
+ /** Regex pattern for vars assignment operator. */
84
+ varsAssignorPattern?: string;
85
+ /** Table of named scripts for execution. */
86
+ scripts?: ScriptsTable;
87
+ }
88
+ /**
89
+ * Definition for a single script entry.
3
90
  */
4
- type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<string, string | {
91
+ interface ScriptDef<TShell extends string | boolean = string | boolean> {
92
+ /** The command string to execute. */
5
93
  cmd: string;
94
+ /** Shell override for this script. */
6
95
  shell?: TShell | undefined;
7
- }>;
8
-
9
- type Scripts = ScriptsTable;
96
+ }
97
+ /**
98
+ * Scripts table shape.
99
+ */
100
+ type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<string, string | ScriptDef<TShell>>;
10
101
 
11
102
  /**
12
- * A minimal representation of an environment key/value mapping.
13
- * Values may be `undefined` to represent "unset".
103
+ * Unify Scripts via the generic ScriptsTable<TShell> so shell types propagate.
14
104
  */
15
- type ProcessEnv = Record<string, string | undefined>;
105
+ type Scripts = ScriptsTable;
16
106
 
17
107
  type GetDotenvConfigResolved = {
18
- dotenvToken?: string;
19
- privateToken?: string;
20
- paths?: string[];
21
- loadProcess?: boolean;
22
- log?: boolean;
23
- shell?: string | boolean;
108
+ /**
109
+ * Help-time/runtime root defaults applied by the host (collapsed families; CLI‑like).
110
+ */
111
+ rootOptionDefaults?: Partial<RootOptionsShape>;
112
+ /**
113
+ * Help-time visibility for root flags; when a key is false the corresponding
114
+ * option(s) are hidden in root help output.
115
+ */
116
+ rootOptionVisibility?: Partial<Record<keyof RootOptionsShape, boolean>>;
117
+ /**
118
+ * Merged scripts table for resolving commands and shell behavior.
119
+ * Entries may be strings or objects with `cmd` and optional `shell`.
120
+ */
24
121
  scripts?: Scripts;
122
+ /**
123
+ * Keys required to be present in the final composed environment.
124
+ * Validation occurs after overlays and dynamics.
125
+ */
25
126
  requiredKeys?: string[];
127
+ /**
128
+ * Optional validation schema (e.g., Zod). When present and it exposes
129
+ * `safeParse(finalEnv)`, the host executes it once after overlays.
130
+ */
26
131
  schema?: unknown;
132
+ /**
133
+ * Public global variables (string‑only).
134
+ */
27
135
  vars?: Record<string, string>;
136
+ /**
137
+ * Public per‑environment variables (string‑only).
138
+ */
28
139
  envVars?: Record<string, Record<string, string>>;
140
+ /**
141
+ * Dynamic variable definitions (JS/TS configs only).
142
+ */
29
143
  dynamic?: unknown;
144
+ /**
145
+ * Per‑plugin configuration slices keyed by realized mount path
146
+ * (for example, "aws/whoami").
147
+ */
30
148
  plugins?: Record<string, unknown>;
31
149
  };
32
150
 
151
+ /**
152
+ * Canonical programmatic options and helpers for get-dotenv.
153
+ *
154
+ * Requirements addressed:
155
+ * - GetDotenvOptions derives from the Zod schema output (single source of truth).
156
+ * - Removed deprecated/compat flags from the public shape (e.g., useConfigLoader).
157
+ * - Provide Vars-aware defineDynamic and a typed config builder defineGetDotenvConfig\<Vars, Env\>().
158
+ * - Preserve existing behavior for defaults resolution and compat converters.
159
+ */
160
+
161
+ /**
162
+ * A minimal representation of an environment key/value mapping.
163
+ * Values may be `undefined` to represent "unset".
164
+ */
165
+ type ProcessEnv = Record<string, string | undefined>;
166
+ /**
167
+ * Dynamic variable function signature. Receives the current expanded variables
168
+ * and the selected environment (if any), and returns either a string to set
169
+ * or `undefined` to unset/skip the variable.
170
+ */
171
+ type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | undefined;
172
+ /**
173
+ * A map of dynamic variable definitions.
174
+ * Keys are variable names; values are either literal strings or functions.
175
+ */
176
+ type GetDotenvDynamic = Record<string, GetDotenvDynamicFunction | ReturnType<GetDotenvDynamicFunction>>;
177
+
178
+ /**
179
+ * Apply a dynamic map to the target progressively.
180
+ * - Functions receive (target, env) and may return string | undefined.
181
+ * - Literals are assigned directly (including undefined).
182
+ */
183
+ declare function applyDynamicMap(target: ProcessEnv, map?: GetDotenvDynamic, env?: string): void;
184
+ /**
185
+ * Load a default-export dynamic map from a JS/TS file and apply it.
186
+ * Uses util/loadModuleDefault for robust TS handling (direct import, esbuild,
187
+ * typescript.transpile fallback).
188
+ *
189
+ * Error behavior:
190
+ * - On failure to load/compile/evaluate the module, throws a unified message:
191
+ * "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
192
+ */
193
+ declare function loadAndApplyDynamic(target: ProcessEnv, absPath: string, env: string | undefined, cacheDirName: string): Promise<void>;
194
+
195
+ /**
196
+ * Configuration sources for environment overlay.
197
+ * Organized by scope (packaged vs project) and privacy (public vs local).
198
+ *
199
+ * @public
200
+ */
33
201
  type OverlayConfigSources = {
202
+ /** Packaged configuration (public). */
34
203
  packaged?: GetDotenvConfigResolved;
204
+ /** Project configuration (public and local). */
35
205
  project?: {
206
+ /** Project public configuration. */
36
207
  public?: GetDotenvConfigResolved;
208
+ /** Project local configuration. */
37
209
  local?: GetDotenvConfigResolved;
38
210
  };
39
211
  };
212
+ /**
213
+ * Base options for overlaying config-provided values onto a dotenv map.
214
+ *
215
+ * @typeParam B - base env shape
216
+ * @public
217
+ */
218
+ interface OverlayEnvOptionsBase<B extends Record<string, string | undefined> | Readonly<Record<string, string | undefined>>> {
219
+ /** Base environment variables. */
220
+ base: B;
221
+ /** Target environment name. */
222
+ env: string | undefined;
223
+ /** Configuration sources to overlay. */
224
+ configs: OverlayConfigSources;
225
+ }
226
+ /**
227
+ * Options including explicit programmatic variables which take top precedence.
228
+ *
229
+ * @typeParam B - base env shape
230
+ * @typeParam P - programmatic vars shape
231
+ * @public
232
+ */
233
+ interface OverlayEnvOptionsWithProgrammatic<B extends Record<string, string | undefined> | Readonly<Record<string, string | undefined>>, P extends Record<string, string | undefined> | Readonly<Record<string, string | undefined>>> extends OverlayEnvOptionsBase<B> {
234
+ programmaticVars: P;
235
+ }
40
236
  /**
41
237
  * Overlay config-provided values onto a base ProcessEnv using precedence axes:
42
238
  * - kind: env \> global
@@ -58,5 +254,5 @@ declare function overlayEnv<B extends ProcessEnv | Readonly<Record<string, strin
58
254
  programmaticVars: P;
59
255
  }): B & P;
60
256
 
61
- export { overlayEnv };
62
- export type { OverlayConfigSources };
257
+ export { applyDynamicMap, loadAndApplyDynamic, overlayEnv };
258
+ export type { OverlayConfigSources, OverlayEnvOptionsBase, OverlayEnvOptionsWithProgrammatic };
@@ -1,3 +1,8 @@
1
+ import fs from 'fs-extra';
2
+ import { createHash } from 'crypto';
3
+ import path from 'path';
4
+ import url from 'url';
5
+
1
6
  /**
2
7
  * Dotenv expansion utilities.
3
8
  *
@@ -122,6 +127,164 @@ function dotenvExpandAll(values, options = {}) {
122
127
  return out;
123
128
  }
124
129
 
130
+ const importDefault = async (fileUrl) => {
131
+ const mod = (await import(fileUrl));
132
+ return mod.default;
133
+ };
134
+ const cacheHash = (absPath, mtimeMs) => createHash('sha1')
135
+ .update(absPath)
136
+ .update(String(mtimeMs))
137
+ .digest('hex')
138
+ .slice(0, 12);
139
+ /**
140
+ * Remove older compiled cache files for a given source base name, keeping
141
+ * at most `keep` most-recent files. Errors are ignored by design.
142
+ */
143
+ const cleanupOldCacheFiles = async (cacheDir, baseName, keep = Math.max(1, Number.parseInt(process.env.GETDOTENV_CACHE_KEEP ?? '2'))) => {
144
+ try {
145
+ const entries = await fs.readdir(cacheDir);
146
+ const mine = entries
147
+ .filter((f) => f.startsWith(`${baseName}.`) && f.endsWith('.mjs'))
148
+ .map((f) => path.join(cacheDir, f));
149
+ if (mine.length <= keep)
150
+ return;
151
+ const stats = await Promise.all(mine.map(async (p) => ({ p, mtimeMs: (await fs.stat(p)).mtimeMs })));
152
+ stats.sort((a, b) => b.mtimeMs - a.mtimeMs);
153
+ const toDelete = stats.slice(keep).map((s) => s.p);
154
+ await Promise.all(toDelete.map(async (p) => {
155
+ try {
156
+ await fs.remove(p);
157
+ }
158
+ catch {
159
+ // best-effort cleanup
160
+ }
161
+ }));
162
+ }
163
+ catch {
164
+ // best-effort cleanup
165
+ }
166
+ };
167
+ /**
168
+ * Load a module default export from a JS/TS file with robust fallbacks:
169
+ * - .js/.mjs/.cjs: direct import * - .ts/.mts/.cts/.tsx:
170
+ * 1) try direct import (if a TS loader is active),
171
+ * 2) esbuild bundle to a temp ESM file,
172
+ * 3) typescript.transpileModule fallback for simple modules.
173
+ *
174
+ * @param absPath - absolute path to source file
175
+ * @param cacheDirName - cache subfolder under .tsbuild
176
+ */
177
+ const loadModuleDefault = async (absPath, cacheDirName) => {
178
+ const ext = path.extname(absPath).toLowerCase();
179
+ const fileUrl = url.pathToFileURL(absPath).toString();
180
+ if (!['.ts', '.mts', '.cts', '.tsx'].includes(ext)) {
181
+ return importDefault(fileUrl);
182
+ }
183
+ // Try direct import first (TS loader active)
184
+ try {
185
+ const dyn = await importDefault(fileUrl);
186
+ if (dyn)
187
+ return dyn;
188
+ }
189
+ catch {
190
+ /* fall through */
191
+ }
192
+ const stat = await fs.stat(absPath);
193
+ const hash = cacheHash(absPath, stat.mtimeMs);
194
+ const cacheDir = path.resolve('.tsbuild', cacheDirName);
195
+ await fs.ensureDir(cacheDir);
196
+ const cacheFile = path.join(cacheDir, `${path.basename(absPath)}.${hash}.mjs`);
197
+ // Try esbuild
198
+ try {
199
+ const esbuild = (await import('esbuild'));
200
+ await esbuild.build({
201
+ entryPoints: [absPath],
202
+ bundle: true,
203
+ platform: 'node',
204
+ format: 'esm',
205
+ target: 'node20',
206
+ outfile: cacheFile,
207
+ sourcemap: false,
208
+ logLevel: 'silent',
209
+ });
210
+ const result = await importDefault(url.pathToFileURL(cacheFile).toString());
211
+ // Best-effort: trim older cache files for this source.
212
+ await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
213
+ return result;
214
+ }
215
+ catch {
216
+ /* fall through to TS transpile */
217
+ }
218
+ // TypeScript transpile fallback
219
+ try {
220
+ const ts = (await import('typescript'));
221
+ const code = await fs.readFile(absPath, 'utf-8');
222
+ const out = ts.transpileModule(code, {
223
+ compilerOptions: {
224
+ module: 'ESNext',
225
+ target: 'ES2022',
226
+ moduleResolution: 'NodeNext',
227
+ },
228
+ }).outputText;
229
+ await fs.writeFile(cacheFile, out, 'utf-8');
230
+ const result = await importDefault(url.pathToFileURL(cacheFile).toString());
231
+ // Best-effort: trim older cache files for this source.
232
+ await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
233
+ return result;
234
+ }
235
+ catch {
236
+ // Caller decides final error wording; rethrow for upstream mapping.
237
+ throw new Error(`Unable to load JS/TS module: ${absPath}. Install 'esbuild' or ensure a TS loader.`);
238
+ }
239
+ };
240
+
241
+ /** src/env/dynamic.ts
242
+ * Helpers for applying and loading dynamic variables (JS/TS).
243
+ *
244
+ * Requirements addressed:
245
+ * - Single service to apply a dynamic map progressively.
246
+ * - Single service to load a JS/TS dynamic module with robust fallbacks (util/loadModuleDefault).
247
+ * - Unify error messaging so callers show consistent guidance.
248
+ */
249
+ /**
250
+ * Apply a dynamic map to the target progressively.
251
+ * - Functions receive (target, env) and may return string | undefined.
252
+ * - Literals are assigned directly (including undefined).
253
+ */
254
+ function applyDynamicMap(target, map, env) {
255
+ if (!map)
256
+ return;
257
+ for (const key of Object.keys(map)) {
258
+ const val = typeof map[key] === 'function'
259
+ ? map[key](target, env)
260
+ : map[key];
261
+ Object.assign(target, { [key]: val });
262
+ }
263
+ }
264
+ /**
265
+ * Load a default-export dynamic map from a JS/TS file and apply it.
266
+ * Uses util/loadModuleDefault for robust TS handling (direct import, esbuild,
267
+ * typescript.transpile fallback).
268
+ *
269
+ * Error behavior:
270
+ * - On failure to load/compile/evaluate the module, throws a unified message:
271
+ * "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
272
+ */
273
+ async function loadAndApplyDynamic(target, absPath, env, cacheDirName) {
274
+ if (!(await fs.exists(absPath)))
275
+ return;
276
+ let dyn;
277
+ try {
278
+ dyn = await loadModuleDefault(absPath, cacheDirName);
279
+ }
280
+ catch {
281
+ // Preserve legacy/clear guidance used by tests and docs.
282
+ throw new Error(`Unable to load dynamic TypeScript file: ${absPath}. ` +
283
+ `Install 'esbuild' (devDependency) to enable TypeScript dynamic modules.`);
284
+ }
285
+ applyDynamicMap(target, dyn, env);
286
+ }
287
+
125
288
  const applyKv = (current, kv) => {
126
289
  if (!kv || Object.keys(kv).length === 0)
127
290
  return current;
@@ -136,7 +299,8 @@ const applyConfigSlice = (current, cfg, env) => {
136
299
  const envKv = env && cfg.envVars ? cfg.envVars[env] : undefined;
137
300
  return applyKv(afterGlobal, envKv);
138
301
  };
139
- function overlayEnv({ base, env, configs, programmaticVars, }) {
302
+ function overlayEnv(args) {
303
+ const { base, env, configs } = args;
140
304
  let current = { ...base };
141
305
  // Source: packaged (public -> local)
142
306
  current = applyConfigSlice(current, configs.packaged, env);
@@ -146,11 +310,11 @@ function overlayEnv({ base, env, configs, programmaticVars, }) {
146
310
  current = applyConfigSlice(current, configs.project?.public, env);
147
311
  current = applyConfigSlice(current, configs.project?.local, env);
148
312
  // Programmatic explicit vars (top of static tier)
149
- if (programmaticVars) {
150
- const toApply = Object.fromEntries(Object.entries(programmaticVars).filter(([_k, v]) => typeof v === 'string'));
313
+ if ('programmaticVars' in args) {
314
+ const toApply = Object.fromEntries(Object.entries(args.programmaticVars).filter(([_k, v]) => typeof v === 'string'));
151
315
  current = applyKv(current, toApply);
152
316
  }
153
317
  return current;
154
318
  }
155
319
 
156
- export { overlayEnv };
320
+ export { applyDynamicMap, loadAndApplyDynamic, overlayEnv };