@chriscode/hush 2.8.2 → 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;AA2jChD,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!**
@@ -413,6 +448,16 @@ Run \`npx hush init\` to generate configuration.
413
448
 
414
449
  ### "No sources defined in hush.yaml"
415
450
  Edit \`hush.yaml\` and add your source files under \`sources:\`.
451
+
452
+ ### "npm warn Unknown project config node-linker"
453
+ This warning appears when running \`npx hush\` in a pnpm workspace because npm doesn't recognize pnpm-specific config in \`.npmrc\`.
454
+
455
+ **Fix:** Add \`loglevel=error\` to the project's \`.npmrc\`:
456
+ \`\`\`bash
457
+ echo "loglevel=error" >> .npmrc
458
+ \`\`\`
459
+
460
+ This suppresses npm warnings while still showing errors. This is a per-project fix for any project using pnpm.
416
461
  `,
417
462
  'REFERENCE.md': `# Hush Command Reference
418
463
 
@@ -548,8 +593,45 @@ Push production secrets to Cloudflare Workers.
548
593
  \`\`\`bash
549
594
  hush push # Push secrets
550
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
551
610
  \`\`\`
552
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
+
553
635
  ---
554
636
 
555
637
  ## Deprecated Commands (Avoid)
@@ -923,25 +1005,46 @@ Tell user: "Your editor will open. Add or modify secrets, then save and close."
923
1005
 
924
1006
  ### "My app can't find DATABASE_URL"
925
1007
 
926
- 1. Check if it exists:
1008
+ 1. **Trace the variable** to see where it exists and where it goes:
927
1009
  \`\`\`bash
928
- npx hush has DATABASE_URL
1010
+ npx hush trace DATABASE_URL
929
1011
  \`\`\`
1012
+ This shows which source files have it and which targets include/exclude it.
930
1013
 
931
- 2. Check target distribution:
1014
+ 2. **Check if it exists** in your current environment:
932
1015
  \`\`\`bash
933
- npx hush inspect
1016
+ npx hush has DATABASE_URL
934
1017
  \`\`\`
935
1018
 
936
- 3. Check hush.yaml for filtering:
1019
+ 3. **Resolve the target** to see what variables it receives:
937
1020
  \`\`\`bash
938
- cat hush.yaml # Safe - this is config, not secrets
1021
+ npx hush resolve api-workers
939
1022
  \`\`\`
940
1023
 
941
- 4. Try running directly:
942
- \`\`\`bash
943
- npx hush run -- env | grep DATABASE
944
- \`\`\`
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.
945
1048
 
946
1049
  ---
947
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.2",
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": {