@chriscode/hush 2.8.3 → 2.9.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.
package/dist/cli.js CHANGED
@@ -15,6 +15,8 @@ import { hasCommand } from './commands/has.js';
15
15
  import { checkCommand } from './commands/check.js';
16
16
  import { skillCommand } from './commands/skill.js';
17
17
  import { keysCommand } from './commands/keys.js';
18
+ import { resolveCommand } from './commands/resolve.js';
19
+ import { traceCommand } from './commands/trace.js';
18
20
  import { findConfigPath, loadConfig, checkSchemaVersion } from './config/loader.js';
19
21
  import { checkForUpdate } from './utils/version-check.js';
20
22
  const require = createRequire(import.meta.url);
@@ -40,6 +42,10 @@ ${pc.bold('Commands:')}
40
42
  status Show configuration and status
41
43
  skill Install Claude Code / OpenCode skill
42
44
  keys <cmd> Manage SOPS age keys (setup, generate, pull, push, list)
45
+
46
+ ${pc.bold('Debugging Commands:')}
47
+ resolve <target> Show what variables a target receives (AI-safe)
48
+ trace <key> Trace a variable through sources and targets (AI-safe)
43
49
 
44
50
  ${pc.bold('Deprecated Commands:')}
45
51
  decrypt Write secrets to disk (unsafe - use 'run' instead)
@@ -47,9 +53,10 @@ ${pc.bold('Deprecated Commands:')}
47
53
  ${pc.bold('Options:')}
48
54
  -e, --env <env> Environment: development or production (default: development)
49
55
  -r, --root <dir> Root directory (default: current directory)
50
- -t, --target <t> Target name from hush.yaml (run only)
56
+ -t, --target <t> Target name from hush.yaml (run/resolve only)
51
57
  -q, --quiet Suppress output (has/check commands)
52
58
  --dry-run Preview changes without applying (push only)
59
+ --verbose Show detailed output (push --dry-run only)
53
60
  --warn Warn but exit 0 on drift (check only)
54
61
  --json Output machine-readable JSON (check only)
55
62
  --only-changed Only check git-modified files (check only)
@@ -107,6 +114,7 @@ function parseArgs(args) {
107
114
  let envExplicit = false;
108
115
  let root = process.cwd();
109
116
  let dryRun = false;
117
+ let verbose = false;
110
118
  let quiet = false;
111
119
  let warn = false;
112
120
  let json = false;
@@ -154,6 +162,10 @@ function parseArgs(args) {
154
162
  dryRun = true;
155
163
  continue;
156
164
  }
165
+ if (arg === '--verbose') {
166
+ verbose = true;
167
+ continue;
168
+ }
157
169
  if (arg === '-q' || arg === '--quiet') {
158
170
  quiet = true;
159
171
  continue;
@@ -230,12 +242,20 @@ function parseArgs(args) {
230
242
  key = arg;
231
243
  continue;
232
244
  }
245
+ if (command === 'trace' && !arg.startsWith('-') && !key) {
246
+ key = arg;
247
+ continue;
248
+ }
249
+ if (command === 'resolve' && !arg.startsWith('-') && !target) {
250
+ target = arg;
251
+ continue;
252
+ }
233
253
  if (command === 'keys' && !arg.startsWith('-') && !subcommand) {
234
254
  subcommand = arg;
235
255
  continue;
236
256
  }
237
257
  }
238
- return { command, subcommand, env, envExplicit, root, dryRun, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, target, cmdArgs };
258
+ return { command, subcommand, env, envExplicit, root, dryRun, verbose, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, target, cmdArgs };
239
259
  }
240
260
  function checkMigrationNeeded(root, command) {
241
261
  const skipCommands = ['', 'help', 'version', 'init', 'skill'];
@@ -271,7 +291,7 @@ async function main() {
271
291
  printHelp();
272
292
  process.exit(0);
273
293
  }
274
- const { command, subcommand, env, envExplicit, root, dryRun, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, target, cmdArgs } = parseArgs(args);
294
+ const { command, subcommand, env, envExplicit, root, dryRun, verbose, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, target, cmdArgs } = parseArgs(args);
275
295
  if (command !== 'run' && !json && !quiet) {
276
296
  checkForUpdate(VERSION);
277
297
  }
@@ -331,7 +351,7 @@ async function main() {
331
351
  await checkCommand({ root, warn, json, quiet, onlyChanged, requireSource, allowPlaintext });
332
352
  break;
333
353
  case 'push':
334
- await pushCommand({ root, dryRun });
354
+ await pushCommand({ root, dryRun, verbose });
335
355
  break;
336
356
  case 'status':
337
357
  await statusCommand({ root });
@@ -347,6 +367,22 @@ async function main() {
347
367
  }
348
368
  await keysCommand({ root, subcommand, vault, force });
349
369
  break;
370
+ case 'resolve':
371
+ if (!target) {
372
+ console.error(pc.red('Usage: hush resolve <target>'));
373
+ console.error(pc.dim('Example: hush resolve api-workers'));
374
+ process.exit(1);
375
+ }
376
+ await resolveCommand({ root, env, target });
377
+ break;
378
+ case 'trace':
379
+ if (!key) {
380
+ console.error(pc.red('Usage: hush trace <KEY>'));
381
+ console.error(pc.dim('Example: hush trace DATABASE_URL'));
382
+ process.exit(1);
383
+ }
384
+ await traceCommand({ root, env, key });
385
+ break;
350
386
  default:
351
387
  if (command) {
352
388
  console.error(pc.red(`Unknown command: ${command}`));
@@ -1 +1 @@
1
- {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAsBvD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiErE"}
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AA0BvD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwErE"}
@@ -8,9 +8,14 @@ import { interpolateVars } from '../core/interpolate.js';
8
8
  import { mergeVars } from '../core/merge.js';
9
9
  import { parseEnvContent } from '../core/parse.js';
10
10
  import { decrypt as sopsDecrypt } from '../core/sops.js';
11
- function pushSecret(key, value, targetDir, dryRun) {
11
+ function pushSecret(key, value, targetDir, dryRun, verbose) {
12
12
  if (dryRun) {
13
- console.log(pc.dim(` [dry-run] ${key}`));
13
+ if (verbose) {
14
+ console.log(pc.green(` + ${key}`));
15
+ }
16
+ else {
17
+ console.log(pc.dim(` [dry-run] ${key}`));
18
+ }
14
19
  return true;
15
20
  }
16
21
  try {
@@ -28,11 +33,14 @@ function pushSecret(key, value, targetDir, dryRun) {
28
33
  }
29
34
  }
30
35
  export async function pushCommand(options) {
31
- const { root, dryRun } = options;
36
+ const { root, dryRun, verbose } = options;
32
37
  const config = loadConfig(root);
33
38
  console.log(pc.blue('Pushing production secrets to Cloudflare Workers...'));
34
39
  if (dryRun) {
35
40
  console.log(pc.yellow('(dry-run mode)'));
41
+ if (verbose) {
42
+ console.log(pc.dim('(verbose output enabled)'));
43
+ }
36
44
  }
37
45
  const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
38
46
  const prodEncrypted = join(root, config.sources.production + '.encrypted');
@@ -59,11 +67,16 @@ export async function pushCommand(options) {
59
67
  for (const target of wranglerTargets) {
60
68
  const targetDir = join(root, target.path);
61
69
  const filtered = filterVarsForTarget(interpolated, target);
62
- console.log(pc.blue(`\n${target.name} (${target.path}/)`));
70
+ if (dryRun && verbose) {
71
+ console.log(pc.blue(`\n[DRY RUN] Would push to ${target.name} (${target.path}/):`));
72
+ }
73
+ else {
74
+ console.log(pc.blue(`\n${target.name} (${target.path}/)`));
75
+ }
63
76
  let success = 0;
64
77
  let failed = 0;
65
78
  for (const { key, value } of filtered) {
66
- if (pushSecret(key, value, targetDir, dryRun)) {
79
+ if (pushSecret(key, value, targetDir, dryRun, verbose)) {
67
80
  if (!dryRun)
68
81
  console.log(pc.green(` ${key}`));
69
82
  success++;
@@ -0,0 +1,8 @@
1
+ import type { Environment } from '../types.js';
2
+ export interface ResolveOptions {
3
+ root: string;
4
+ env: Environment;
5
+ target: string;
6
+ }
7
+ export declare function resolveCommand(options: ResolveOptions): Promise<void>;
8
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/commands/resolve.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,WAAW,EAAU,MAAM,aAAa,CAAC;AAG/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AA6BD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmG3E"}
@@ -0,0 +1,111 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import pc from 'picocolors';
4
+ import { loadConfig } from '../config/loader.js';
5
+ import { interpolateVars } from '../core/interpolate.js';
6
+ import { mergeVars } from '../core/merge.js';
7
+ import { parseEnvContent } from '../core/parse.js';
8
+ import { decrypt as sopsDecrypt } from '../core/sops.js';
9
+ import { FORMAT_OUTPUT_FILES } from '../types.js';
10
+ function matchesPattern(key, pattern) {
11
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
12
+ return regex.test(key);
13
+ }
14
+ function matchesAnyPattern(key, patterns) {
15
+ for (const pattern of patterns) {
16
+ if (matchesPattern(key, pattern)) {
17
+ return pattern;
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ function getOutputFilename(target, env) {
23
+ if (target.filename) {
24
+ return target.filename;
25
+ }
26
+ return FORMAT_OUTPUT_FILES[target.format][env];
27
+ }
28
+ export async function resolveCommand(options) {
29
+ const { root, env, target: targetName } = options;
30
+ const config = loadConfig(root);
31
+ const target = config.targets.find(t => t.name === targetName);
32
+ if (!target) {
33
+ console.error(pc.red(`Target not found: ${targetName}`));
34
+ console.error(pc.dim('Available targets: ' + config.targets.map(t => t.name).join(', ')));
35
+ process.exit(1);
36
+ }
37
+ const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
38
+ const envEncrypted = join(root, config.sources[env] + '.encrypted');
39
+ const localEncrypted = join(root, config.sources.local + '.encrypted');
40
+ const varsBySource = new Map();
41
+ const allVars = new Map();
42
+ const loadSource = (path, sourceName) => {
43
+ if (!existsSync(path))
44
+ return;
45
+ const content = sopsDecrypt(path);
46
+ const vars = parseEnvContent(content);
47
+ const sourceVars = [];
48
+ for (const v of vars) {
49
+ const varSource = { key: v.key, value: v.value, source: sourceName };
50
+ sourceVars.push(varSource);
51
+ allVars.set(v.key, varSource);
52
+ }
53
+ varsBySource.set(sourceName, sourceVars);
54
+ };
55
+ loadSource(sharedEncrypted, config.sources.shared);
56
+ loadSource(envEncrypted, config.sources[env]);
57
+ loadSource(localEncrypted, config.sources.local);
58
+ if (allVars.size === 0) {
59
+ console.error(pc.red('No encrypted files found'));
60
+ process.exit(1);
61
+ }
62
+ const merged = mergeVars(...Array.from(varsBySource.values()).map(sources => sources.map(s => ({ key: s.key, value: s.value }))));
63
+ const interpolated = interpolateVars(merged);
64
+ const included = [];
65
+ const excluded = [];
66
+ for (const v of interpolated) {
67
+ const varSource = allVars.get(v.key);
68
+ const source = varSource?.source || 'unknown';
69
+ if (target.include && target.include.length > 0) {
70
+ const matchedInclude = matchesAnyPattern(v.key, target.include);
71
+ if (!matchedInclude) {
72
+ excluded.push({ key: v.key, pattern: `not in include: ${target.include.join(', ')}` });
73
+ continue;
74
+ }
75
+ }
76
+ if (target.exclude && target.exclude.length > 0) {
77
+ const matchedExclude = matchesAnyPattern(v.key, target.exclude);
78
+ if (matchedExclude) {
79
+ excluded.push({ key: v.key, pattern: matchedExclude });
80
+ continue;
81
+ }
82
+ }
83
+ included.push({ key: v.key, source });
84
+ }
85
+ const outputFile = getOutputFilename(target, env);
86
+ console.log(pc.bold(`\nTarget: ${pc.cyan(target.name)}`));
87
+ console.log(`Path: ${pc.dim(target.path + '/')}`);
88
+ console.log(`Format: ${pc.dim(target.format)} ${pc.dim(`(${outputFile})`)}`);
89
+ console.log(`Environment: ${pc.dim(env)}`);
90
+ console.log(pc.green(`\n✅ INCLUDED VARIABLES (${included.length}):`));
91
+ if (included.length === 0) {
92
+ console.log(pc.dim(' (none)'));
93
+ }
94
+ else {
95
+ const maxKeyLen = Math.max(...included.map(v => v.key.length));
96
+ for (const v of included) {
97
+ console.log(` ${v.key.padEnd(maxKeyLen)} ${pc.dim(`(source: ${v.source})`)}`);
98
+ }
99
+ }
100
+ console.log(pc.red(`\n🚫 EXCLUDED VARIABLES (${excluded.length}):`));
101
+ if (excluded.length === 0) {
102
+ console.log(pc.dim(' (none)'));
103
+ }
104
+ else {
105
+ const maxKeyLen = Math.max(...excluded.map(v => v.key.length));
106
+ for (const v of excluded) {
107
+ console.log(` ${v.key.padEnd(maxKeyLen)} ${pc.dim(`(matches: ${v.pattern})`)}`);
108
+ }
109
+ }
110
+ console.log('');
111
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqkChD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvE"}
1
+ {"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkqChD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvE"}
@@ -105,6 +105,41 @@ npx hush run -e production -- npm build # Production
105
105
 
106
106
  ---
107
107
 
108
+ ## Debugging Secret Issues
109
+
110
+ When a variable is missing or not appearing where expected, use these commands:
111
+
112
+ ### Why is my variable missing from a target?
113
+
114
+ \`\`\`bash
115
+ npx hush resolve <target> # See what variables a target receives
116
+ npx hush resolve api-workers # Example: check api-workers target
117
+ npx hush resolve api-workers -e prod # Check with production env
118
+ \`\`\`
119
+
120
+ This shows:
121
+ - ✅ **Included variables** - what the target will receive
122
+ - 🚫 **Excluded variables** - what was filtered out and WHY (which pattern matched)
123
+
124
+ ### Where does a specific variable go?
125
+
126
+ \`\`\`bash
127
+ npx hush trace <KEY> # Trace a variable through all targets
128
+ npx hush trace DATABASE_URL # Example: trace DATABASE_URL
129
+ \`\`\`
130
+
131
+ This shows:
132
+ - Which source files contain the variable
133
+ - Which targets include/exclude it and why
134
+
135
+ ### Preview what would be pushed
136
+
137
+ \`\`\`bash
138
+ npx hush push --dry-run --verbose # See exactly what would be pushed
139
+ \`\`\`
140
+
141
+ ---
142
+
108
143
  ## Running Programs with Secrets
109
144
 
110
145
  **This is the primary way to use secrets - they never touch disk!**
@@ -558,8 +593,45 @@ Push production secrets to Cloudflare Workers.
558
593
  \`\`\`bash
559
594
  hush push # Push secrets
560
595
  hush push --dry-run # Preview without pushing
596
+ hush push --dry-run --verbose # Detailed preview of what would be pushed
597
+ \`\`\`
598
+
599
+ ---
600
+
601
+ ## Debugging Commands
602
+
603
+ ### hush resolve <target>
604
+
605
+ Show what variables a specific target will receive, with filtering details.
606
+
607
+ \`\`\`bash
608
+ hush resolve api-workers # Check api-workers target
609
+ hush resolve api-workers -e prod # Check with production environment
561
610
  \`\`\`
562
611
 
612
+ **Output shows:**
613
+ - ✅ Included variables (with source file)
614
+ - 🚫 Excluded variables (with matching pattern)
615
+
616
+ **Use when:** A target is missing expected variables
617
+
618
+ ---
619
+
620
+ ### hush trace <KEY>
621
+
622
+ Trace a specific variable through all sources and targets.
623
+
624
+ \`\`\`bash
625
+ hush trace DATABASE_URL # Trace DATABASE_URL
626
+ hush trace STRIPE_SECRET_KEY # Trace another variable
627
+ \`\`\`
628
+
629
+ **Output shows:**
630
+ - Which source files contain the variable
631
+ - Which targets include/exclude it (and why)
632
+
633
+ **Use when:** You need to understand why a variable appears in some places but not others
634
+
563
635
  ---
564
636
 
565
637
  ## Deprecated Commands (Avoid)
@@ -933,25 +1005,46 @@ Tell user: "Your editor will open. Add or modify secrets, then save and close."
933
1005
 
934
1006
  ### "My app can't find DATABASE_URL"
935
1007
 
936
- 1. Check if it exists:
1008
+ 1. **Trace the variable** to see where it exists and where it goes:
937
1009
  \`\`\`bash
938
- npx hush has DATABASE_URL
1010
+ npx hush trace DATABASE_URL
939
1011
  \`\`\`
1012
+ This shows which source files have it and which targets include/exclude it.
940
1013
 
941
- 2. Check target distribution:
1014
+ 2. **Check if it exists** in your current environment:
942
1015
  \`\`\`bash
943
- npx hush inspect
1016
+ npx hush has DATABASE_URL
944
1017
  \`\`\`
945
1018
 
946
- 3. Check hush.yaml for filtering:
1019
+ 3. **Resolve the target** to see what variables it receives:
947
1020
  \`\`\`bash
948
- cat hush.yaml # Safe - this is config, not secrets
1021
+ npx hush resolve api-workers
949
1022
  \`\`\`
950
1023
 
951
- 4. Try running directly:
952
- \`\`\`bash
953
- npx hush run -- env | grep DATABASE
954
- \`\`\`
1024
+ ### "Target is missing expected variables"
1025
+
1026
+ \`\`\`bash
1027
+ npx hush resolve <target-name> # See included/excluded variables
1028
+ npx hush resolve <target-name> -e prod # Check production
1029
+ \`\`\`
1030
+
1031
+ Look at the 🚫 EXCLUDED section to see which pattern is filtering out your variable.
1032
+
1033
+ ### "Variable appears in wrong places"
1034
+
1035
+ \`\`\`bash
1036
+ npx hush trace <VARIABLE_NAME>
1037
+ \`\`\`
1038
+
1039
+ This shows the full disposition across all targets - which include it and which exclude it.
1040
+
1041
+ ### "Push is missing some secrets"
1042
+
1043
+ \`\`\`bash
1044
+ npx hush push --dry-run --verbose
1045
+ \`\`\`
1046
+
1047
+ This shows exactly what would be pushed to each target.
955
1048
 
956
1049
  ---
957
1050
 
@@ -0,0 +1,8 @@
1
+ import type { Environment } from '../types.js';
2
+ export interface TraceOptions {
3
+ root: string;
4
+ env: Environment;
5
+ key: string;
6
+ }
7
+ export declare function traceCommand(options: TraceOptions): Promise<void>;
8
+ //# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/commands/trace.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAuCD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DvE"}
@@ -0,0 +1,87 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import pc from 'picocolors';
4
+ import { loadConfig } from '../config/loader.js';
5
+ import { parseEnvContent } from '../core/parse.js';
6
+ import { decrypt as sopsDecrypt } from '../core/sops.js';
7
+ function matchesPattern(key, pattern) {
8
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
9
+ return regex.test(key);
10
+ }
11
+ function matchesAnyPattern(key, patterns) {
12
+ for (const pattern of patterns) {
13
+ if (matchesPattern(key, pattern)) {
14
+ return pattern;
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function getTargetDisposition(key, target) {
20
+ if (target.include && target.include.length > 0) {
21
+ const matchedInclude = matchesAnyPattern(key, target.include);
22
+ if (!matchedInclude) {
23
+ return { status: 'not_included', reason: `not in include: ${target.include.join(', ')}` };
24
+ }
25
+ }
26
+ if (target.exclude && target.exclude.length > 0) {
27
+ const matchedExclude = matchesAnyPattern(key, target.exclude);
28
+ if (matchedExclude) {
29
+ return { status: 'excluded', reason: `matches exclude: ${matchedExclude}` };
30
+ }
31
+ }
32
+ return { status: 'included' };
33
+ }
34
+ export async function traceCommand(options) {
35
+ const { root, env, key } = options;
36
+ const config = loadConfig(root);
37
+ console.log(pc.bold(`\nTracing variable: ${pc.cyan(key)}\n`));
38
+ console.log(pc.blue('Source Status:'));
39
+ const sources = [
40
+ { name: config.sources.shared, path: join(root, config.sources.shared + '.encrypted'), found: false },
41
+ { name: config.sources.development, path: join(root, config.sources.development + '.encrypted'), found: false },
42
+ { name: config.sources.production, path: join(root, config.sources.production + '.encrypted'), found: false },
43
+ { name: config.sources.local, path: join(root, config.sources.local + '.encrypted'), found: false },
44
+ ];
45
+ const maxSourceLen = Math.max(...sources.map(s => s.name.length));
46
+ for (const source of sources) {
47
+ if (!existsSync(source.path)) {
48
+ console.log(` ${source.name.padEnd(maxSourceLen)} : ${pc.dim('(file not found)')}`);
49
+ continue;
50
+ }
51
+ try {
52
+ const content = sopsDecrypt(source.path);
53
+ const vars = parseEnvContent(content);
54
+ const found = vars.some(v => v.key === key);
55
+ source.found = found;
56
+ if (found) {
57
+ console.log(` ${source.name.padEnd(maxSourceLen)} : ${pc.green('✅ Present')}`);
58
+ }
59
+ else {
60
+ console.log(` ${source.name.padEnd(maxSourceLen)} : ${pc.dim('❌ Not found')}`);
61
+ }
62
+ }
63
+ catch {
64
+ console.log(` ${source.name.padEnd(maxSourceLen)} : ${pc.red('⚠️ Decrypt failed')}`);
65
+ }
66
+ }
67
+ const foundInAnySource = sources.some(s => s.found);
68
+ console.log(pc.blue(`\nTarget Disposition (Environment: ${env}):`));
69
+ const maxTargetLen = Math.max(...config.targets.map(t => t.name.length));
70
+ for (const target of config.targets) {
71
+ const disposition = getTargetDisposition(key, target);
72
+ const targetLabel = `[${target.name}]`.padEnd(maxTargetLen + 2);
73
+ if (!foundInAnySource) {
74
+ console.log(` ${targetLabel} : ${pc.yellow('⚠️ Variable not in any source')}`);
75
+ }
76
+ else if (disposition.status === 'included') {
77
+ console.log(` ${targetLabel} : ${pc.green('✅ Included')}`);
78
+ }
79
+ else if (disposition.status === 'excluded') {
80
+ console.log(` ${targetLabel} : ${pc.red(`🚫 Excluded`)} ${pc.dim(`(${disposition.reason})`)}`);
81
+ }
82
+ else {
83
+ console.log(` ${targetLabel} : ${pc.red(`🚫 Not included`)} ${pc.dim(`(${disposition.reason})`)}`);
84
+ }
85
+ }
86
+ console.log('');
87
+ }
package/dist/types.d.ts CHANGED
@@ -50,6 +50,7 @@ export interface RunOptions {
50
50
  export interface PushOptions {
51
51
  root: string;
52
52
  dryRun: boolean;
53
+ verbose: boolean;
53
54
  }
54
55
  export interface StatusOptions {
55
56
  root: string;
@@ -94,6 +95,16 @@ export interface SkillOptions {
94
95
  global?: boolean;
95
96
  local?: boolean;
96
97
  }
98
+ export interface ResolveOptions {
99
+ root: string;
100
+ env: Environment;
101
+ target: string;
102
+ }
103
+ export interface TraceOptions {
104
+ root: string;
105
+ env: Environment;
106
+ key: string;
107
+ }
97
108
  export declare const DEFAULT_SOURCES: SourceFiles;
98
109
  export declare const FORMAT_OUTPUT_FILES: Record<OutputFormat, Record<Environment, string>>;
99
110
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chriscode/hush",
3
- "version": "2.8.3",
3
+ "version": "2.9.0",
4
4
  "description": "SOPS-based secrets management for monorepos. Encrypt once, decrypt everywhere.",
5
5
  "type": "module",
6
6
  "bin": {