@karmaniverous/get-dotenv 5.1.0 → 5.2.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- > **_Load, expand, and compose environment variables from a deterministic dotenv cascade, then execute commands under that context. Use `get-dotenv` as a library, a CLI, or a plugin-first host to build dotenv-aware tooling with cross‑platform shell control, CI‑friendly capture, and clear diagnostics._**
1
+ > Load, expand, and compose environment variables from a deterministic dotenv cascade, then execute commands under that context. Use `get-dotenv` as a library, a CLI, or a plugin-first host to build dotenv-aware tooling with cross‑platform shell control, CI‑friendly capture, and clear diagnostics.
2
2
 
3
3
  # get-dotenv
4
4
 
@@ -34,6 +34,20 @@ Load environment variables with a cascade of environment-aware dotenv files. You
34
34
 
35
35
  ✅ Set defaults for all options in a `getdotenv.config.json` file in your project root directory.
36
36
 
37
+ ✅ Validate your final composed environment via config: JSON/YAML `requiredKeys` or a JS/TS Zod `schema`. Validation runs once after Phase C (interpolation). Use `--strict` to fail on issues; otherwise warnings are printed. See the [Config files and overlays](./guides/config.md) guide.
38
+
39
+ ✅ Diagnostics for safer visibility without altering runtime values:
40
+
41
+ - Redaction at presentation time for secret-like keys (`--redact`, `--redact-pattern`).
42
+ - Entropy warnings (on by default) for high-entropy strings; gated by length/printability and tunable via `--entropy-*` flags. See [Config files and overlays](./guides/config.md).
43
+
44
+ ✅ Clear tracing and CI-friendly capture:
45
+
46
+ - `--trace [keys...]` shows per-key origin (parent | dotenv | unset) before spawning.
47
+ - Set `GETDOTENV_STDIO=pipe` or use `--capture` to buffer child stdout/stderr deterministically. See [Shell execution behavior](./guides/shell.md).
48
+
49
+ ✅ Cross-platform shells and normalized child environments: defaults to `/bin/bash` on POSIX and `powershell.exe` on Windows; subprocess env is composed once via a unified helper that drops undefineds and normalizes temp/home variables. See [Shell execution behavior](./guides/shell.md).
50
+
37
51
  ✅ [Generate an extensible `getdotenv`-based CLI](https://github.com/karmaniverous/get-dotenv-child) for use in your own projects.
38
52
 
39
53
  `getdotenv` relies on the excellent [`dotenv`](https://www.npmjs.com/package/dotenv) parser and somewhat improves on [`dotenv-expand`](https://www.npmjs.com/package/dotenv-expand) for recursive variable expansion.
@@ -118,6 +132,42 @@ Options can be passed programmatically or set in a `getdotenv.config.json` file
118
132
 
119
133
  See the [child CLI example repo](https://github.com/karmaniverous/get-dotenv-child#configuration) for an extensive discussion of the various config options and how & where to set them.
120
134
 
135
+ ## CLI embedding (createCli)
136
+
137
+ Prefer the named factory when you want to embed the get‑dotenv CLI in your own tool. It wires the plugin‑first host with the included plugins and returns a small runner.
138
+
139
+ ```ts
140
+ #!/usr/bin/env node
141
+ import { createCli } from '@karmaniverous/get-dotenv';
142
+
143
+ // Build a CLI and run with your argv; alias appears in help.
144
+ await createCli({
145
+ alias: 'mycli',
146
+ // Optional: override the help header (otherwise “mycli v<version>” is used).
147
+ branding: 'mycli v1.2.3',
148
+ }).run(process.argv.slice(2));
149
+ ```
150
+
151
+ Notes:
152
+
153
+ - The host resolves dotenv context once per invocation and exposes subcommands: cmd, batch, aws, and init (see Guides below).
154
+ - Use `--trace [keys...]` and `--redact` for diagnostics without altering runtime values.
155
+ - Default shells are normalized across platforms: `/bin/bash` on POSIX and `powershell.exe` on Windows (overridable per‑script or globally).
156
+ - Help/exit behavior (important for embedding and tests):
157
+ - To keep `-h/--help` deterministic across ESM/CJS and avoid Commander’s default `process.exit`, `createCli().run(['-h'])` prints help and returns without exiting the process.
158
+ - Because help is printed before the internal `brand()` call runs, the optional branding header may be omitted when `-h/--help` is used at the top level. If you need a branded header, prefer `mycli help` (which runs through normal parsing) or construct and brand a host directly (see “Branding the host CLI” in Guides).
159
+ - Interop matrix (embedding in different module systems):
160
+ - ESM dynamic:
161
+ ```ts
162
+ const { createCli } = await import('@karmaniverous/get-dotenv');
163
+ await createCli({ alias: 'mycli' }).run(['-h']);
164
+ ```
165
+ - CommonJS require (using built outputs for smoke checks):
166
+ ```js
167
+ const { createCli } = require('@karmaniverous/get-dotenv/dist/index.cjs');
168
+ createCli({ alias: 'mycli' }).run(['-h']);
169
+ ```
170
+
121
171
  ## Dynamic Processing
122
172
 
123
173
  This package supports the full [`dotenv-expand`](https://www.npmjs.com/package/dotenv-expand) syntax, with some internal performance improvements. Dynamic variables can be authored in JS or TS.
package/dist/cliHost.cjs CHANGED
@@ -38,6 +38,11 @@ const baseRootOptionDefaults = {
38
38
  dotenvToken: '.env',
39
39
  loadProcess: true,
40
40
  logger: console,
41
+ // Diagnostics defaults
42
+ warnEntropy: true,
43
+ entropyThreshold: 3.8,
44
+ entropyMinLength: 16,
45
+ entropyWhitelist: ['^GIT_', '^npm_', '^CI$', 'SHLVL'],
41
46
  paths: './',
42
47
  pathsDelimiter: ' ',
43
48
  privateToken: 'local',
@@ -58,7 +63,7 @@ const baseRootOptionDefaults = {
58
63
  const baseGetDotenvCliOptions = baseRootOptionDefaults;
59
64
 
60
65
  /** @internal */
61
- const isPlainObject = (value) => value !== null &&
66
+ const isPlainObject$1 = (value) => value !== null &&
62
67
  typeof value === 'object' &&
63
68
  Object.getPrototypeOf(value) === Object.prototype;
64
69
  const mergeInto = (target, source) => {
@@ -66,10 +71,10 @@ const mergeInto = (target, source) => {
66
71
  if (sVal === undefined)
67
72
  continue; // do not overwrite with undefined
68
73
  const tVal = target[key];
69
- if (isPlainObject(tVal) && isPlainObject(sVal)) {
74
+ if (isPlainObject$1(tVal) && isPlainObject$1(sVal)) {
70
75
  target[key] = mergeInto({ ...tVal }, sVal);
71
76
  }
72
- else if (isPlainObject(sVal)) {
77
+ else if (isPlainObject$1(sVal)) {
73
78
  target[key] = mergeInto({}, sVal);
74
79
  }
75
80
  else {
@@ -229,11 +234,12 @@ const getDotenvOptionsSchemaRaw = zod.z.object({
229
234
  const getDotenvOptionsSchemaResolved = getDotenvOptionsSchemaRaw;
230
235
 
231
236
  /**
232
- * Zod schemas for configuration files discovered by the new loader. *
237
+ * Zod schemas for configuration files discovered by the new loader.
238
+ *
233
239
  * Notes:
234
240
  * - RAW: all fields optional; shapes are stringly-friendly (paths may be string[] or string).
235
241
  * - RESOLVED: normalized shapes (paths always string[]).
236
- * - For this step (JSON/YAML only), any defined `dynamic` will be rejected by the loader.
242
+ * - For JSON/YAML configs, the loader rejects "dynamic" and "schema" (JS/TS-only).
237
243
  */
238
244
  // String-only env value map
239
245
  const stringMap = zod.z.record(zod.z.string(), zod.z.string());
@@ -248,6 +254,8 @@ const getDotenvConfigSchemaRaw = zod.z.object({
248
254
  log: zod.z.boolean().optional(),
249
255
  shell: zod.z.union([zod.z.string(), zod.z.boolean()]).optional(),
250
256
  scripts: zod.z.record(zod.z.string(), zod.z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
257
+ requiredKeys: zod.z.array(zod.z.string()).optional(),
258
+ schema: zod.z.unknown().optional(), // JS/TS-only; loader rejects in JSON/YAML
251
259
  vars: stringMap.optional(), // public, global
252
260
  envVars: envStringMap.optional(), // public, per-env
253
261
  // Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
@@ -430,9 +438,11 @@ const loadConfigFile = async (filePath) => {
430
438
  .join('\n');
431
439
  throw new Error(`Invalid config ${filePath}:\n${msgs}`);
432
440
  }
433
- // Disallow dynamic in JSON/YAML; allow in JS/TS
434
- if (!isJsOrTs(filePath) && parsed.data.dynamic !== undefined) {
435
- throw new Error(`Config ${filePath} specifies "dynamic"; JSON/YAML configs cannot include dynamic in this step. Use JS/TS config.`);
441
+ // Disallow dynamic and schema in JSON/YAML; allow both in JS/TS.
442
+ if (!isJsOrTs(filePath) &&
443
+ (parsed.data.dynamic !== undefined || parsed.data.schema !== undefined)) {
444
+ throw new Error(`Config ${filePath} specifies unsupported keys for JSON/YAML. ` +
445
+ `Use JS/TS config for "dynamic" or "schema".`);
436
446
  }
437
447
  return getDotenvConfigSchemaResolved.parse(parsed.data);
438
448
  };
@@ -620,6 +630,78 @@ const overlayEnv = ({ base, env, configs, programmaticVars, }) => {
620
630
  return current;
621
631
  };
622
632
 
633
+ /** src/diagnostics/entropy.ts
634
+ * Entropy diagnostics (presentation-only).
635
+ * - Gated by min length and printable ASCII.
636
+ * - Warn once per key per run when bits/char \>= threshold.
637
+ * - Supports whitelist patterns to suppress known-noise keys.
638
+ */
639
+ const warned = new Set();
640
+ const isPrintableAscii = (s) => /^[\x20-\x7E]+$/.test(s);
641
+ const compile$1 = (patterns) => (patterns ?? []).map((p) => new RegExp(p, 'i'));
642
+ const whitelisted = (key, regs) => regs.some((re) => re.test(key));
643
+ const shannonBitsPerChar = (s) => {
644
+ const freq = new Map();
645
+ for (const ch of s)
646
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
647
+ const n = s.length;
648
+ let h = 0;
649
+ for (const c of freq.values()) {
650
+ const p = c / n;
651
+ h -= p * Math.log2(p);
652
+ }
653
+ return h;
654
+ };
655
+ /**
656
+ * Maybe emit a one-line entropy warning for a key.
657
+ * Caller supplies an `emit(line)` function; the helper ensures once-per-key.
658
+ */
659
+ const maybeWarnEntropy = (key, value, origin, opts, emit) => {
660
+ if (!opts || opts.warnEntropy === false)
661
+ return;
662
+ if (warned.has(key))
663
+ return;
664
+ const v = value ?? '';
665
+ const minLen = Math.max(0, opts.entropyMinLength ?? 16);
666
+ const threshold = opts.entropyThreshold ?? 3.8;
667
+ if (v.length < minLen)
668
+ return;
669
+ if (!isPrintableAscii(v))
670
+ return;
671
+ const wl = compile$1(opts.entropyWhitelist);
672
+ if (whitelisted(key, wl))
673
+ return;
674
+ const bpc = shannonBitsPerChar(v);
675
+ if (bpc >= threshold) {
676
+ warned.add(key);
677
+ emit(`[entropy] key=${key} score=${bpc.toFixed(2)} len=${String(v.length)} origin=${origin}`);
678
+ }
679
+ };
680
+
681
+ const DEFAULT_PATTERNS = [
682
+ '\\bsecret\\b',
683
+ '\\btoken\\b',
684
+ '\\bpass(word)?\\b',
685
+ '\\bapi[_-]?key\\b',
686
+ '\\bkey\\b',
687
+ ];
688
+ const compile = (patterns) => (patterns && patterns.length > 0 ? patterns : DEFAULT_PATTERNS).map((p) => new RegExp(p, 'i'));
689
+ const shouldRedactKey = (key, regs) => regs.some((re) => re.test(key));
690
+ const MASK = '[redacted]';
691
+ /**
692
+ * Produce a shallow redacted copy of an env-like object for display.
693
+ */
694
+ const redactObject = (obj, opts) => {
695
+ if (!opts?.redact)
696
+ return { ...obj };
697
+ const regs = compile(opts.redactPatterns);
698
+ const out = {};
699
+ for (const [k, v] of Object.entries(obj)) {
700
+ out[k] = v && shouldRedactKey(k, regs) ? MASK : v;
701
+ }
702
+ return out;
703
+ };
704
+
623
705
  /**
624
706
  * Asynchronously read a dotenv file & parse it into an object.
625
707
  *
@@ -869,14 +951,103 @@ const getDotenv = async (options = {}) => {
869
951
  resultDotenv = dotenvForOutput;
870
952
  }
871
953
  // Log result.
872
- if (log)
873
- logger.log(resultDotenv);
954
+ if (log) {
955
+ const redactFlag = options.redact ?? false;
956
+ const redactPatterns = options.redactPatterns ?? undefined;
957
+ const redOpts = {};
958
+ if (redactFlag)
959
+ redOpts.redact = true;
960
+ if (redactFlag && Array.isArray(redactPatterns))
961
+ redOpts.redactPatterns = redactPatterns;
962
+ const bag = redactFlag
963
+ ? redactObject(resultDotenv, redOpts)
964
+ : { ...resultDotenv };
965
+ logger.log(bag);
966
+ // Entropy warnings: once-per-key-per-run (presentation only)
967
+ const warnEntropyVal = options.warnEntropy ?? true;
968
+ const entropyThresholdVal = options
969
+ .entropyThreshold;
970
+ const entropyMinLengthVal = options
971
+ .entropyMinLength;
972
+ const entropyWhitelistVal = options
973
+ .entropyWhitelist;
974
+ const entOpts = {};
975
+ if (typeof warnEntropyVal === 'boolean')
976
+ entOpts.warnEntropy = warnEntropyVal;
977
+ if (typeof entropyThresholdVal === 'number')
978
+ entOpts.entropyThreshold = entropyThresholdVal;
979
+ if (typeof entropyMinLengthVal === 'number')
980
+ entOpts.entropyMinLength = entropyMinLengthVal;
981
+ if (Array.isArray(entropyWhitelistVal))
982
+ entOpts.entropyWhitelist = entropyWhitelistVal;
983
+ for (const [k, v] of Object.entries(resultDotenv)) {
984
+ maybeWarnEntropy(k, v, v !== undefined ? 'dotenv' : 'unset', entOpts, (line) => {
985
+ logger.log(line);
986
+ });
987
+ }
988
+ }
874
989
  // Load process.env.
875
990
  if (loadProcess)
876
991
  Object.assign(process.env, resultDotenv);
877
992
  return resultDotenv;
878
993
  };
879
994
 
995
+ /**
996
+ * Deep interpolation utility for string leaves.
997
+ * - Expands string values using dotenv-style expansion against the provided envRef.
998
+ * - Preserves non-strings as-is.
999
+ * - Does not recurse into arrays (arrays are returned unchanged).
1000
+ *
1001
+ * Intended for:
1002
+ * - Phase C option/config interpolation after composing ctx.dotenv.
1003
+ * - Per-plugin config slice interpolation before afterResolve.
1004
+ */
1005
+ /** @internal */
1006
+ const isPlainObject = (v) => v !== null &&
1007
+ typeof v === 'object' &&
1008
+ !Array.isArray(v) &&
1009
+ Object.getPrototypeOf(v) === Object.prototype;
1010
+ /**
1011
+ * Deeply interpolate string leaves against envRef.
1012
+ * Arrays are not recursed into; they are returned unchanged.
1013
+ *
1014
+ * @typeParam T - Shape of the input value.
1015
+ * @param value - Input value (object/array/primitive).
1016
+ * @param envRef - Reference environment for interpolation.
1017
+ * @returns A new value with string leaves interpolated.
1018
+ */
1019
+ const interpolateDeep = (value, envRef) => {
1020
+ // Strings: expand and return
1021
+ if (typeof value === 'string') {
1022
+ const out = dotenvExpand(value, envRef);
1023
+ // dotenvExpand returns string | undefined; preserve original on undefined
1024
+ return (out ?? value);
1025
+ }
1026
+ // Arrays: return as-is (no recursion)
1027
+ if (Array.isArray(value)) {
1028
+ return value;
1029
+ }
1030
+ // Plain objects: shallow clone and recurse into values
1031
+ if (isPlainObject(value)) {
1032
+ const src = value;
1033
+ const out = {};
1034
+ for (const [k, v] of Object.entries(src)) {
1035
+ // Recurse for strings/objects; keep arrays as-is; preserve other scalars
1036
+ if (typeof v === 'string')
1037
+ out[k] = dotenvExpand(v, envRef) ?? v;
1038
+ else if (Array.isArray(v))
1039
+ out[k] = v;
1040
+ else if (isPlainObject(v))
1041
+ out[k] = interpolateDeep(v, envRef);
1042
+ else
1043
+ out[k] = v;
1044
+ }
1045
+ return out;
1046
+ }
1047
+ // Other primitives/types: return as-is
1048
+ return value;
1049
+ };
1050
+
880
1051
  /**
881
1052
  * Compute the dotenv context for the host (uses the config loader/overlay path).
882
1053
  * - Resolves and validates options strictly (host-only).
@@ -960,19 +1131,32 @@ const computeContext = async (customOptions, plugins, hostMetaUrl) => {
960
1131
  {};
961
1132
  const mergedPluginConfigs = defaultsDeep({}, packagedPlugins, publicPlugins, localPlugins);
962
1133
  for (const p of plugins) {
963
- if (!p.id || !p.configSchema)
1134
+ if (!p.id)
964
1135
  continue;
965
1136
  const slice = mergedPluginConfigs[p.id];
966
1137
  if (slice === undefined)
967
1138
  continue;
968
- const parsed = p.configSchema.safeParse(slice);
969
- if (!parsed.success) {
970
- const msgs = parsed.error.issues
971
- .map((i) => `${i.path.join('.')}: ${i.message}`)
972
- .join('\n');
973
- throw new Error(`Invalid config for plugin '${p.id}':\n${msgs}`);
1139
+ // Per-plugin interpolation just before validation/afterResolve:
1140
+ // precedence: process.env wins over ctx.dotenv for slice defaults.
1141
+ const envRef = {
1142
+ ...dotenv,
1143
+ ...process.env,
1144
+ };
1145
+ const interpolated = interpolateDeep(slice, envRef);
1146
+ // Validate if a schema is provided; otherwise accept interpolated slice as-is.
1147
+ if (p.configSchema) {
1148
+ const parsed = p.configSchema.safeParse(interpolated);
1149
+ if (!parsed.success) {
1150
+ const msgs = parsed.error.issues
1151
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
1152
+ .join('\n');
1153
+ throw new Error(`Invalid config for plugin '${p.id}':\n${msgs}`);
1154
+ }
1155
+ mergedPluginConfigs[p.id] = parsed.data;
1156
+ }
1157
+ else {
1158
+ mergedPluginConfigs[p.id] = interpolated;
974
1159
  }
975
- mergedPluginConfigs[p.id] = parsed.data;
976
1160
  }
977
1161
  return {
978
1162
  optionsResolved: validated,
@@ -1016,10 +1200,23 @@ class GetDotenvCli extends commander.Command {
1016
1200
  visibleOptions: (cmd) => {
1017
1201
  const all = cmd.options ??
1018
1202
  [];
1019
- return all.filter((opt) => {
1203
+ const base = all.filter((opt) => {
1020
1204
  const group = opt.__group;
1021
1205
  return group === 'base';
1022
1206
  });
1207
+ // Sort: short-aliased options first, then long-only; stable by flags.
1208
+ const hasShort = (opt) => {
1209
+ const flags = opt.flags ?? '';
1210
+ // Matches "-x," or starting "-x " before any long
1211
+ return /(^|\s|,)-[A-Za-z]/.test(flags);
1212
+ };
1213
+ const byFlags = (opt) => opt.flags ?? '';
1214
+ base.sort((a, b) => {
1215
+ const aS = hasShort(a) ? 1 : 0;
1216
+ const bS = hasShort(b) ? 1 : 0;
1217
+ return bS - aS || byFlags(a).localeCompare(byFlags(b));
1218
+ });
1219
+ return base;
1023
1220
  },
1024
1221
  });
1025
1222
  this.addHelpText('beforeAll', () => {
@@ -1208,6 +1405,12 @@ class GetDotenvCli extends commander.Command {
1208
1405
  return '';
1209
1406
  const renderRows = (title, rows) => {
1210
1407
  const width = Math.min(40, rows.reduce((m, r) => Math.max(m, r.flags.length), 0));
1408
+ // Sort within group: short-aliased flags first
1409
+ rows.sort((a, b) => {
1410
+ const aS = /(^|\s|,)-[A-Za-z]/.test(a.flags) ? 1 : 0;
1411
+ const bS = /(^|\s|,)-[A-Za-z]/.test(b.flags) ? 1 : 0;
1412
+ return bS - aS || a.flags.localeCompare(b.flags);
1413
+ });
1211
1414
  const lines = rows
1212
1415
  .map((r) => {
1213
1416
  const pad = ' '.repeat(Math.max(2, width - r.flags.length + 2));
@@ -115,6 +115,23 @@ interface GetDotenvCliOptions extends Omit<GetDotenvOptions, 'paths' | 'vars'> {
115
115
  * Logs CLI internals when true.
116
116
  */
117
117
  debug?: boolean;
118
+ /**
119
+ * Strict mode: fail the run when env validation issues are detected
120
+ * (schema or requiredKeys). Warns by default when false or unset.
121
+ */
122
+ strict?: boolean;
123
+ /**
124
+ * Redaction (presentation): mask secret-like values in logs/trace.
125
+ */
126
+ redact?: boolean;
127
+ /**
128
+ * Entropy warnings (presentation): emit once-per-key warnings for high-entropy values.
129
+ */
130
+ warnEntropy?: boolean;
131
+ entropyThreshold?: number;
132
+ entropyMinLength?: number;
133
+ entropyWhitelist?: string[];
134
+ redactPatterns?: string[];
118
135
  /**
119
136
  * When true, capture child stdout/stderr and re-emit after completion.
120
137
  * Useful for tests/CI. Default behavior is streaming via stdio: 'inherit'.
@@ -115,6 +115,23 @@ interface GetDotenvCliOptions extends Omit<GetDotenvOptions, 'paths' | 'vars'> {
115
115
  * Logs CLI internals when true.
116
116
  */
117
117
  debug?: boolean;
118
+ /**
119
+ * Strict mode: fail the run when env validation issues are detected
120
+ * (schema or requiredKeys). Warns by default when false or unset.
121
+ */
122
+ strict?: boolean;
123
+ /**
124
+ * Redaction (presentation): mask secret-like values in logs/trace.
125
+ */
126
+ redact?: boolean;
127
+ /**
128
+ * Entropy warnings (presentation): emit once-per-key warnings for high-entropy values.
129
+ */
130
+ warnEntropy?: boolean;
131
+ entropyThreshold?: number;
132
+ entropyMinLength?: number;
133
+ entropyWhitelist?: string[];
134
+ redactPatterns?: string[];
118
135
  /**
119
136
  * When true, capture child stdout/stderr and re-emit after completion.
120
137
  * Useful for tests/CI. Default behavior is streaming via stdio: 'inherit'.
package/dist/cliHost.d.ts CHANGED
@@ -115,6 +115,23 @@ interface GetDotenvCliOptions extends Omit<GetDotenvOptions, 'paths' | 'vars'> {
115
115
  * Logs CLI internals when true.
116
116
  */
117
117
  debug?: boolean;
118
+ /**
119
+ * Strict mode: fail the run when env validation issues are detected
120
+ * (schema or requiredKeys). Warns by default when false or unset.
121
+ */
122
+ strict?: boolean;
123
+ /**
124
+ * Redaction (presentation): mask secret-like values in logs/trace.
125
+ */
126
+ redact?: boolean;
127
+ /**
128
+ * Entropy warnings (presentation): emit once-per-key warnings for high-entropy values.
129
+ */
130
+ warnEntropy?: boolean;
131
+ entropyThreshold?: number;
132
+ entropyMinLength?: number;
133
+ entropyWhitelist?: string[];
134
+ redactPatterns?: string[];
118
135
  /**
119
136
  * When true, capture child stdout/stderr and re-emit after completion.
120
137
  * Useful for tests/CI. Default behavior is streaming via stdio: 'inherit'.