@karmaniverous/get-dotenv 6.0.0-1 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +91 -379
  2. package/dist/cli.d.ts +569 -0
  3. package/dist/cli.mjs +18877 -0
  4. package/dist/cliHost.d.ts +528 -184
  5. package/dist/cliHost.mjs +1977 -1428
  6. package/dist/config.d.ts +191 -14
  7. package/dist/config.mjs +266 -81
  8. package/dist/env-overlay.d.ts +223 -16
  9. package/dist/env-overlay.mjs +185 -4
  10. package/dist/getdotenv.cli.mjs +18025 -3196
  11. package/dist/index.d.ts +623 -256
  12. package/dist/index.mjs +18045 -3206
  13. package/dist/plugins-aws.d.ts +221 -91
  14. package/dist/plugins-aws.mjs +2411 -369
  15. package/dist/plugins-batch.d.ts +300 -103
  16. package/dist/plugins-batch.mjs +2560 -484
  17. package/dist/plugins-cmd.d.ts +229 -106
  18. package/dist/plugins-cmd.mjs +2518 -790
  19. package/dist/plugins-init.d.ts +221 -95
  20. package/dist/plugins-init.mjs +2170 -105
  21. package/dist/plugins.d.ts +246 -125
  22. package/dist/plugins.mjs +17941 -1968
  23. package/dist/templates/cli/index.ts +25 -0
  24. package/{templates/cli/ts → dist/templates/cli}/plugins/hello.ts +13 -9
  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 +42 -0
  39. package/dist/templates/index.ts +25 -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 +42 -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 +25 -0
  54. package/templates/cli/plugins/hello.ts +42 -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 +8 -3
  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,249 @@
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
+ * @param target - Mutable target environment to assign into.
184
+ * @param map - Dynamic map to apply (functions and/or literal values).
185
+ * @param env - Selected environment name (if any) passed through to dynamic functions.
186
+ * @returns Nothing.
187
+ */
188
+ declare function applyDynamicMap(target: ProcessEnv, map?: GetDotenvDynamic, env?: string): void;
189
+ /**
190
+ * Load a default-export dynamic map from a JS/TS file and apply it.
191
+ * Uses util/loadModuleDefault for robust TS handling (direct import, esbuild,
192
+ * typescript.transpile fallback).
193
+ *
194
+ * Error behavior:
195
+ * - On failure to load/compile/evaluate the module, throws a unified message:
196
+ * "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
197
+ *
198
+ * @param target - Mutable target environment to assign into.
199
+ * @param absPath - Absolute path to the dynamic module file.
200
+ * @param env - Selected environment name (if any).
201
+ * @param cacheDirName - Cache subdirectory under `.tsbuild/` for compiled artifacts.
202
+ * @returns A `Promise\<void\>` which resolves after the module (if present) has been applied.
203
+ */
204
+ declare function loadAndApplyDynamic(target: ProcessEnv, absPath: string, env: string | undefined, cacheDirName: string): Promise<void>;
205
+
206
+ /**
207
+ * Configuration sources for environment overlay.
208
+ * Organized by scope (packaged vs project) and privacy (public vs local).
209
+ *
210
+ * @public
211
+ */
33
212
  type OverlayConfigSources = {
213
+ /** Packaged configuration (public). */
34
214
  packaged?: GetDotenvConfigResolved;
215
+ /** Project configuration (public and local). */
35
216
  project?: {
217
+ /** Project public configuration. */
36
218
  public?: GetDotenvConfigResolved;
219
+ /** Project local configuration. */
37
220
  local?: GetDotenvConfigResolved;
38
221
  };
39
222
  };
223
+ /**
224
+ * Base options for overlaying config-provided values onto a dotenv map.
225
+ *
226
+ * @typeParam B - base env shape
227
+ * @public
228
+ */
229
+ interface OverlayEnvOptionsBase<B extends Record<string, string | undefined> | Readonly<Record<string, string | undefined>>> {
230
+ /** Base environment variables. */
231
+ base: B;
232
+ /** Target environment name. */
233
+ env: string | undefined;
234
+ /** Configuration sources to overlay. */
235
+ configs: OverlayConfigSources;
236
+ }
237
+ /**
238
+ * Options including explicit programmatic variables which take top precedence.
239
+ *
240
+ * @typeParam B - base env shape
241
+ * @typeParam P - programmatic vars shape
242
+ * @public
243
+ */
244
+ 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> {
245
+ programmaticVars: P;
246
+ }
40
247
  /**
41
248
  * Overlay config-provided values onto a base ProcessEnv using precedence axes:
42
249
  * - kind: env \> global
@@ -58,5 +265,5 @@ declare function overlayEnv<B extends ProcessEnv | Readonly<Record<string, strin
58
265
  programmaticVars: P;
59
266
  }): B & P;
60
267
 
61
- export { overlayEnv };
62
- export type { OverlayConfigSources };
268
+ export { applyDynamicMap, loadAndApplyDynamic, overlayEnv };
269
+ 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,181 @@ 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
+ *
170
+ * Behavior by extension:
171
+ *
172
+ * - `.js`/`.mjs`/`.cjs`: direct dynamic import.
173
+ * - `.ts`/`.mts`/`.cts`/`.tsx`:
174
+ * - try direct dynamic import (when a TS loader is active),
175
+ * - else compile via `esbuild` to a cached `.mjs` file and import,
176
+ * - else fallback to `typescript.transpileModule` for simple modules.
177
+ *
178
+ * @typeParam T - Type of the expected default export.
179
+ * @param absPath - Absolute path to the source file.
180
+ * @param cacheDirName - Cache subfolder under `.tsbuild/`.
181
+ * @returns A `Promise\<T | undefined\>` resolving to the default export (if any).
182
+ */
183
+ const loadModuleDefault = async (absPath, cacheDirName) => {
184
+ const ext = path.extname(absPath).toLowerCase();
185
+ const fileUrl = url.pathToFileURL(absPath).toString();
186
+ if (!['.ts', '.mts', '.cts', '.tsx'].includes(ext)) {
187
+ return importDefault(fileUrl);
188
+ }
189
+ // Try direct import first (TS loader active)
190
+ try {
191
+ const dyn = await importDefault(fileUrl);
192
+ if (dyn)
193
+ return dyn;
194
+ }
195
+ catch {
196
+ /* fall through */
197
+ }
198
+ const stat = await fs.stat(absPath);
199
+ const hash = cacheHash(absPath, stat.mtimeMs);
200
+ const cacheDir = path.resolve('.tsbuild', cacheDirName);
201
+ await fs.ensureDir(cacheDir);
202
+ const cacheFile = path.join(cacheDir, `${path.basename(absPath)}.${hash}.mjs`);
203
+ // Try esbuild
204
+ try {
205
+ const esbuild = (await import('esbuild'));
206
+ await esbuild.build({
207
+ entryPoints: [absPath],
208
+ bundle: true,
209
+ platform: 'node',
210
+ format: 'esm',
211
+ target: 'node20',
212
+ outfile: cacheFile,
213
+ sourcemap: false,
214
+ logLevel: 'silent',
215
+ });
216
+ const result = await importDefault(url.pathToFileURL(cacheFile).toString());
217
+ // Best-effort: trim older cache files for this source.
218
+ await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
219
+ return result;
220
+ }
221
+ catch {
222
+ /* fall through to TS transpile */
223
+ }
224
+ // TypeScript transpile fallback
225
+ try {
226
+ const ts = (await import('typescript'));
227
+ const code = await fs.readFile(absPath, 'utf-8');
228
+ const out = ts.transpileModule(code, {
229
+ compilerOptions: {
230
+ module: 'ESNext',
231
+ target: 'ES2022',
232
+ moduleResolution: 'NodeNext',
233
+ },
234
+ }).outputText;
235
+ await fs.writeFile(cacheFile, out, 'utf-8');
236
+ const result = await importDefault(url.pathToFileURL(cacheFile).toString());
237
+ // Best-effort: trim older cache files for this source.
238
+ await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
239
+ return result;
240
+ }
241
+ catch {
242
+ // Caller decides final error wording; rethrow for upstream mapping.
243
+ throw new Error(`Unable to load JS/TS module: ${absPath}. Install 'esbuild' or ensure a TS loader.`);
244
+ }
245
+ };
246
+
247
+ /** src/env/dynamic.ts
248
+ * Helpers for applying and loading dynamic variables (JS/TS).
249
+ *
250
+ * Requirements addressed:
251
+ * - Single service to apply a dynamic map progressively.
252
+ * - Single service to load a JS/TS dynamic module with robust fallbacks (util/loadModuleDefault).
253
+ * - Unify error messaging so callers show consistent guidance.
254
+ */
255
+ /**
256
+ * Apply a dynamic map to the target progressively.
257
+ * - Functions receive (target, env) and may return string | undefined.
258
+ * - Literals are assigned directly (including undefined).
259
+ *
260
+ * @param target - Mutable target environment to assign into.
261
+ * @param map - Dynamic map to apply (functions and/or literal values).
262
+ * @param env - Selected environment name (if any) passed through to dynamic functions.
263
+ * @returns Nothing.
264
+ */
265
+ function applyDynamicMap(target, map, env) {
266
+ if (!map)
267
+ return;
268
+ for (const key of Object.keys(map)) {
269
+ const val = typeof map[key] === 'function'
270
+ ? map[key](target, env)
271
+ : map[key];
272
+ Object.assign(target, { [key]: val });
273
+ }
274
+ }
275
+ /**
276
+ * Load a default-export dynamic map from a JS/TS file and apply it.
277
+ * Uses util/loadModuleDefault for robust TS handling (direct import, esbuild,
278
+ * typescript.transpile fallback).
279
+ *
280
+ * Error behavior:
281
+ * - On failure to load/compile/evaluate the module, throws a unified message:
282
+ * "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
283
+ *
284
+ * @param target - Mutable target environment to assign into.
285
+ * @param absPath - Absolute path to the dynamic module file.
286
+ * @param env - Selected environment name (if any).
287
+ * @param cacheDirName - Cache subdirectory under `.tsbuild/` for compiled artifacts.
288
+ * @returns A `Promise\<void\>` which resolves after the module (if present) has been applied.
289
+ */
290
+ async function loadAndApplyDynamic(target, absPath, env, cacheDirName) {
291
+ if (!(await fs.exists(absPath)))
292
+ return;
293
+ let dyn;
294
+ try {
295
+ dyn = await loadModuleDefault(absPath, cacheDirName);
296
+ }
297
+ catch {
298
+ // Preserve legacy/clear guidance used by tests and docs.
299
+ throw new Error(`Unable to load dynamic TypeScript file: ${absPath}. ` +
300
+ `Install 'esbuild' (devDependency) to enable TypeScript dynamic modules.`);
301
+ }
302
+ applyDynamicMap(target, dyn, env);
303
+ }
304
+
125
305
  const applyKv = (current, kv) => {
126
306
  if (!kv || Object.keys(kv).length === 0)
127
307
  return current;
@@ -136,7 +316,8 @@ const applyConfigSlice = (current, cfg, env) => {
136
316
  const envKv = env && cfg.envVars ? cfg.envVars[env] : undefined;
137
317
  return applyKv(afterGlobal, envKv);
138
318
  };
139
- function overlayEnv({ base, env, configs, programmaticVars, }) {
319
+ function overlayEnv(args) {
320
+ const { base, env, configs } = args;
140
321
  let current = { ...base };
141
322
  // Source: packaged (public -> local)
142
323
  current = applyConfigSlice(current, configs.packaged, env);
@@ -146,11 +327,11 @@ function overlayEnv({ base, env, configs, programmaticVars, }) {
146
327
  current = applyConfigSlice(current, configs.project?.public, env);
147
328
  current = applyConfigSlice(current, configs.project?.local, env);
148
329
  // Programmatic explicit vars (top of static tier)
149
- if (programmaticVars) {
150
- const toApply = Object.fromEntries(Object.entries(programmaticVars).filter(([_k, v]) => typeof v === 'string'));
330
+ if ('programmaticVars' in args) {
331
+ const toApply = Object.fromEntries(Object.entries(args.programmaticVars).filter(([_k, v]) => typeof v === 'string'));
151
332
  current = applyKv(current, toApply);
152
333
  }
153
334
  return current;
154
335
  }
155
336
 
156
- export { overlayEnv };
337
+ export { applyDynamicMap, loadAndApplyDynamic, overlayEnv };