@chriscode/hush 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/cli.js +189 -74
  2. package/dist/commands/check.d.ts +4 -0
  3. package/dist/commands/check.d.ts.map +1 -0
  4. package/dist/commands/check.js +249 -0
  5. package/dist/commands/decrypt.d.ts +1 -9
  6. package/dist/commands/decrypt.d.ts.map +1 -1
  7. package/dist/commands/decrypt.js +54 -88
  8. package/dist/commands/edit.d.ts +1 -7
  9. package/dist/commands/edit.d.ts.map +1 -1
  10. package/dist/commands/edit.js +13 -19
  11. package/dist/commands/encrypt.d.ts +1 -7
  12. package/dist/commands/encrypt.d.ts.map +1 -1
  13. package/dist/commands/encrypt.js +23 -19
  14. package/dist/commands/has.d.ts +9 -0
  15. package/dist/commands/has.d.ts.map +1 -0
  16. package/dist/commands/has.js +45 -0
  17. package/dist/commands/init.d.ts +3 -0
  18. package/dist/commands/init.d.ts.map +1 -0
  19. package/dist/commands/init.js +63 -0
  20. package/dist/commands/inspect.d.ts +7 -0
  21. package/dist/commands/inspect.d.ts.map +1 -0
  22. package/dist/commands/inspect.js +50 -0
  23. package/dist/commands/list.d.ts +3 -0
  24. package/dist/commands/list.d.ts.map +1 -0
  25. package/dist/commands/list.js +35 -0
  26. package/dist/commands/push.d.ts +1 -8
  27. package/dist/commands/push.d.ts.map +1 -1
  28. package/dist/commands/push.js +45 -63
  29. package/dist/commands/status.d.ts +1 -7
  30. package/dist/commands/status.d.ts.map +1 -1
  31. package/dist/commands/status.js +43 -45
  32. package/dist/config/loader.d.ts +5 -0
  33. package/dist/config/loader.d.ts.map +1 -0
  34. package/dist/config/loader.js +50 -0
  35. package/dist/core/filter.d.ts +4 -0
  36. package/dist/core/filter.d.ts.map +1 -0
  37. package/dist/core/filter.js +27 -0
  38. package/dist/core/interpolate.d.ts +6 -0
  39. package/dist/core/interpolate.d.ts.map +1 -0
  40. package/dist/core/interpolate.js +43 -0
  41. package/dist/core/mask.d.ts +11 -0
  42. package/dist/core/mask.d.ts.map +1 -0
  43. package/dist/core/mask.js +30 -0
  44. package/dist/core/merge.d.ts +3 -0
  45. package/dist/core/merge.d.ts.map +1 -0
  46. package/dist/core/merge.js +9 -0
  47. package/dist/core/parse.d.ts +3 -27
  48. package/dist/core/parse.d.ts.map +1 -1
  49. package/dist/core/parse.js +8 -77
  50. package/dist/core/sops.d.ts +1 -12
  51. package/dist/core/sops.d.ts.map +1 -1
  52. package/dist/core/sops.js +4 -25
  53. package/dist/formats/dotenv.d.ts +3 -0
  54. package/dist/formats/dotenv.d.ts.map +1 -0
  55. package/dist/formats/dotenv.js +3 -0
  56. package/dist/formats/index.d.ts +8 -0
  57. package/dist/formats/index.d.ts.map +1 -0
  58. package/dist/formats/index.js +17 -0
  59. package/dist/formats/json.d.ts +3 -0
  60. package/dist/formats/json.d.ts.map +1 -0
  61. package/dist/formats/json.js +7 -0
  62. package/dist/formats/shell.d.ts +3 -0
  63. package/dist/formats/shell.d.ts.map +1 -0
  64. package/dist/formats/shell.js +11 -0
  65. package/dist/formats/wrangler.d.ts +3 -0
  66. package/dist/formats/wrangler.d.ts.map +1 -0
  67. package/dist/formats/wrangler.js +3 -0
  68. package/dist/index.d.ts +17 -7
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +15 -9
  71. package/dist/lib/diff.d.ts +12 -0
  72. package/dist/lib/diff.d.ts.map +1 -0
  73. package/dist/lib/diff.js +19 -0
  74. package/dist/types.d.ts +65 -31
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +22 -6
  77. package/package.json +15 -14
  78. package/README.md +0 -194
  79. package/dist/core/discover.d.ts +0 -6
  80. package/dist/core/discover.d.ts.map +0 -1
  81. package/dist/core/discover.js +0 -81
package/dist/cli.js CHANGED
@@ -1,107 +1,222 @@
1
1
  #!/usr/bin/env node
2
- import { resolve } from 'node:path';
3
2
  import pc from 'picocolors';
4
3
  import { decryptCommand } from './commands/decrypt.js';
5
- import { editCommand } from './commands/edit.js';
6
4
  import { encryptCommand } from './commands/encrypt.js';
7
- import { pushCommand } from './commands/push.js';
5
+ import { editCommand } from './commands/edit.js';
8
6
  import { statusCommand } from './commands/status.js';
9
- const HELP = `
7
+ import { pushCommand } from './commands/push.js';
8
+ import { initCommand } from './commands/init.js';
9
+ import { listCommand } from './commands/list.js';
10
+ import { inspectCommand } from './commands/inspect.js';
11
+ import { hasCommand } from './commands/has.js';
12
+ import { checkCommand } from './commands/check.js';
13
+ const VERSION = '2.0.0';
14
+ function printHelp() {
15
+ console.log(`
10
16
  ${pc.bold('hush')} - SOPS-based secrets management for monorepos
11
17
 
12
18
  ${pc.bold('Usage:')}
13
19
  hush <command> [options]
14
20
 
15
21
  ${pc.bold('Commands:')}
16
- decrypt Decrypt .env.encrypted and generate env files for all packages
17
- encrypt Encrypt .env to .env.encrypted
18
- push Push production secrets to Cloudflare Workers
19
- edit Open .env.encrypted in editor (SOPS inline edit)
20
- status Show discovered packages and their styles
21
- help Show this help message
22
+ init Initialize hush.yaml config
23
+ encrypt Encrypt source .env files
24
+ decrypt Decrypt and distribute to targets
25
+ edit [file] Edit encrypted file in $EDITOR
26
+ list List all variables (shows values)
27
+ inspect List all variables (masked values, AI-safe)
28
+ has <key> Check if a secret exists (exit 0 if set, 1 if not)
29
+ check Verify secrets are encrypted (for pre-commit hooks)
30
+ push Push secrets to Cloudflare Workers
31
+ status Show configuration and status
22
32
 
23
33
  ${pc.bold('Options:')}
24
- --env <dev|prod> Target environment (default: dev)
25
- --dry-run Don't make changes, just show what would happen
26
- --root <path> Monorepo root directory (default: cwd)
34
+ -e, --env <env> Environment: development or production (default: development)
35
+ -r, --root <dir> Root directory (default: current directory)
36
+ -q, --quiet Suppress output (has/check commands)
37
+ --dry-run Preview changes without applying (push only)
38
+ --warn Warn but exit 0 on drift (check only)
39
+ --json Output machine-readable JSON (check only)
40
+ --only-changed Only check git-modified files (check only)
41
+ --require-source Fail if source file is missing (check only)
42
+ -h, --help Show this help message
43
+ -v, --version Show version number
27
44
 
28
45
  ${pc.bold('Examples:')}
29
- hush decrypt Decrypt for development
30
- hush decrypt --env prod Decrypt for production
31
- hush encrypt Encrypt .env file
32
- hush push Push prod secrets to Wrangler
33
- hush push --dry-run Preview what would be pushed
34
- hush edit Edit encrypted file in $EDITOR
35
- hush status Show package detection info
36
- `;
46
+ hush init Initialize hush.yaml config
47
+ hush encrypt Encrypt .env files
48
+ hush decrypt Decrypt for development
49
+ hush decrypt -e production Decrypt for production
50
+ hush edit Edit shared secrets
51
+ hush edit development Edit development secrets
52
+ hush list List all variables (shows values)
53
+ hush inspect List all variables (masked, AI-safe)
54
+ hush has DATABASE_URL Check if DATABASE_URL is set
55
+ hush has API_KEY -q && echo "API_KEY is configured"
56
+ hush check Verify secrets are encrypted
57
+ hush check --warn Check but don't fail on drift
58
+ hush check --json Output JSON for CI
59
+ hush push --dry-run Preview push to Cloudflare
60
+ hush status Show current status
61
+ `);
62
+ }
63
+ function parseEnvironment(value) {
64
+ if (value === 'development' || value === 'dev')
65
+ return 'development';
66
+ if (value === 'production' || value === 'prod')
67
+ return 'production';
68
+ return null;
69
+ }
70
+ function parseFileKey(value) {
71
+ if (value === 'shared' || value === 'development' || value === 'production')
72
+ return value;
73
+ if (value === 'dev')
74
+ return 'development';
75
+ if (value === 'prod')
76
+ return 'production';
77
+ return null;
78
+ }
37
79
  function parseArgs(args) {
38
- const result = {
39
- command: 'help',
40
- env: 'dev',
41
- dryRun: false,
42
- root: process.cwd(),
43
- };
80
+ let command = '';
81
+ let env = 'development';
82
+ let root = process.cwd();
83
+ let dryRun = false;
84
+ let quiet = false;
85
+ let warn = false;
86
+ let json = false;
87
+ let onlyChanged = false;
88
+ let requireSource = false;
89
+ let file;
90
+ let key;
44
91
  for (let i = 0; i < args.length; i++) {
45
92
  const arg = args[i];
46
- if (arg === '--env' && args[i + 1]) {
47
- const envArg = args[i + 1];
48
- if (envArg === 'dev' || envArg === 'prod') {
49
- result.env = envArg;
93
+ if (arg === '-h' || arg === '--help') {
94
+ printHelp();
95
+ process.exit(0);
96
+ }
97
+ if (arg === '-v' || arg === '--version') {
98
+ console.log(VERSION);
99
+ process.exit(0);
100
+ }
101
+ if (arg === '-e' || arg === '--env') {
102
+ const nextArg = args[++i];
103
+ const parsed = parseEnvironment(nextArg);
104
+ if (parsed) {
105
+ env = parsed;
50
106
  }
51
107
  else {
52
- console.error(pc.red(`Invalid environment: ${envArg}`));
53
- console.error(pc.dim('Valid values: dev, prod'));
108
+ console.error(pc.red(`Invalid environment: ${nextArg}`));
109
+ console.error(pc.dim('Use: development, dev, production, or prod'));
54
110
  process.exit(1);
55
111
  }
56
- i++;
112
+ continue;
113
+ }
114
+ if (arg === '-r' || arg === '--root') {
115
+ root = args[++i];
116
+ continue;
117
+ }
118
+ if (arg === '--dry-run') {
119
+ dryRun = true;
120
+ continue;
121
+ }
122
+ if (arg === '-q' || arg === '--quiet') {
123
+ quiet = true;
124
+ continue;
57
125
  }
58
- else if (arg === '--dry-run') {
59
- result.dryRun = true;
126
+ if (arg === '--warn') {
127
+ warn = true;
128
+ continue;
60
129
  }
61
- else if (arg === '--root' && args[i + 1]) {
62
- result.root = resolve(args[i + 1]);
63
- i++;
130
+ if (arg === '--json') {
131
+ json = true;
132
+ continue;
64
133
  }
65
- else if (!arg.startsWith('-') && !result.command) {
66
- result.command = arg;
134
+ if (arg === '--only-changed') {
135
+ onlyChanged = true;
136
+ continue;
67
137
  }
68
- else if (!arg.startsWith('-')) {
69
- result.command = arg;
138
+ if (arg === '--require-source') {
139
+ requireSource = true;
140
+ continue;
141
+ }
142
+ if (!command && !arg.startsWith('-')) {
143
+ command = arg;
144
+ continue;
145
+ }
146
+ if (command === 'edit' && !arg.startsWith('-')) {
147
+ const parsed = parseFileKey(arg);
148
+ if (parsed) {
149
+ file = parsed;
150
+ }
151
+ else {
152
+ console.error(pc.red(`Invalid file: ${arg}`));
153
+ console.error(pc.dim('Use: shared, development, or production'));
154
+ process.exit(1);
155
+ }
156
+ continue;
157
+ }
158
+ if (command === 'has' && !arg.startsWith('-') && !key) {
159
+ key = arg;
160
+ continue;
70
161
  }
71
162
  }
72
- return result;
163
+ return { command, env, root, dryRun, quiet, warn, json, onlyChanged, requireSource, file, key };
73
164
  }
74
165
  async function main() {
75
166
  const args = process.argv.slice(2);
76
- const parsed = parseArgs(args);
77
- switch (parsed.command) {
78
- case 'decrypt':
79
- await decryptCommand({ root: parsed.root, env: parsed.env });
80
- break;
81
- case 'encrypt':
82
- await encryptCommand({ root: parsed.root });
83
- break;
84
- case 'push':
85
- await pushCommand({ root: parsed.root, dryRun: parsed.dryRun });
86
- break;
87
- case 'edit':
88
- await editCommand({ root: parsed.root });
89
- break;
90
- case 'status':
91
- await statusCommand({ root: parsed.root });
92
- break;
93
- case 'help':
94
- case '--help':
95
- case '-h':
96
- console.log(HELP);
97
- break;
98
- default:
99
- console.error(pc.red(`Unknown command: ${parsed.command}`));
100
- console.log(HELP);
101
- process.exit(1);
167
+ if (args.length === 0) {
168
+ printHelp();
169
+ process.exit(0);
170
+ }
171
+ const { command, env, root, dryRun, quiet, warn, json, onlyChanged, requireSource, file, key } = parseArgs(args);
172
+ try {
173
+ switch (command) {
174
+ case 'init':
175
+ await initCommand({ root });
176
+ break;
177
+ case 'encrypt':
178
+ await encryptCommand({ root });
179
+ break;
180
+ case 'decrypt':
181
+ await decryptCommand({ root, env });
182
+ break;
183
+ case 'edit':
184
+ await editCommand({ root, file });
185
+ break;
186
+ case 'list':
187
+ await listCommand({ root, env });
188
+ break;
189
+ case 'inspect':
190
+ await inspectCommand({ root, env });
191
+ break;
192
+ case 'has':
193
+ if (!key) {
194
+ console.error(pc.red('Usage: hush has <KEY>'));
195
+ process.exit(1);
196
+ }
197
+ await hasCommand({ root, env, key, quiet });
198
+ break;
199
+ case 'check':
200
+ await checkCommand({ root, warn, json, quiet, onlyChanged, requireSource });
201
+ break;
202
+ case 'push':
203
+ await pushCommand({ root, dryRun });
204
+ break;
205
+ case 'status':
206
+ await statusCommand({ root });
207
+ break;
208
+ default:
209
+ if (command) {
210
+ console.error(pc.red(`Unknown command: ${command}`));
211
+ }
212
+ printHelp();
213
+ process.exit(1);
214
+ }
215
+ }
216
+ catch (error) {
217
+ const err = error;
218
+ console.error(pc.red(`Error: ${err.message}`));
219
+ process.exit(1);
102
220
  }
103
221
  }
104
- main().catch((error) => {
105
- console.error(pc.red('Fatal error:'), error.message);
106
- process.exit(1);
107
- });
222
+ main();
@@ -0,0 +1,4 @@
1
+ import type { CheckOptions, CheckResult } from '../types.js';
2
+ export declare function check(options: CheckOptions): Promise<CheckResult>;
3
+ export declare function checkCommand(options: CheckOptions): Promise<void>;
4
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAmB,WAAW,EAAc,MAAM,aAAa,CAAC;AA+C1F,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BvE;AAmLD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvE"}
@@ -0,0 +1,249 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import pc from 'picocolors';
5
+ import { loadConfig, findConfigPath } from '../config/loader.js';
6
+ import { parseEnvContent } from '../core/parse.js';
7
+ import { decrypt as sopsDecrypt, isSopsInstalled } from '../core/sops.js';
8
+ import { computeDiff, isInSync } from '../lib/diff.js';
9
+ function getSourceEncryptedPairs(config) {
10
+ const pairs = [];
11
+ if (config.sources.shared) {
12
+ pairs.push({
13
+ source: config.sources.shared,
14
+ encrypted: config.sources.shared + '.encrypted',
15
+ sourceKey: 'shared',
16
+ });
17
+ }
18
+ if (config.sources.development) {
19
+ pairs.push({
20
+ source: config.sources.development,
21
+ encrypted: config.sources.development + '.encrypted',
22
+ sourceKey: 'development',
23
+ });
24
+ }
25
+ if (config.sources.production) {
26
+ pairs.push({
27
+ source: config.sources.production,
28
+ encrypted: config.sources.production + '.encrypted',
29
+ sourceKey: 'production',
30
+ });
31
+ }
32
+ return pairs;
33
+ }
34
+ function getGitChangedFiles(root) {
35
+ try {
36
+ const staged = execSync('git diff --cached --name-only', { cwd: root, encoding: 'utf-8' });
37
+ const unstaged = execSync('git diff --name-only', { cwd: root, encoding: 'utf-8' });
38
+ const files = [...staged.split('\n'), ...unstaged.split('\n')].filter(Boolean);
39
+ return new Set(files);
40
+ }
41
+ catch {
42
+ return new Set();
43
+ }
44
+ }
45
+ export async function check(options) {
46
+ const { root, requireSource, onlyChanged } = options;
47
+ if (!isSopsInstalled()) {
48
+ return {
49
+ status: 'error',
50
+ files: [{
51
+ source: '',
52
+ encrypted: '',
53
+ inSync: false,
54
+ added: [],
55
+ removed: [],
56
+ changed: [],
57
+ error: 'SOPS_NOT_INSTALLED',
58
+ }],
59
+ };
60
+ }
61
+ const configPath = findConfigPath(root);
62
+ if (!configPath) {
63
+ const config = loadConfig(root);
64
+ const pairs = getSourceEncryptedPairs(config);
65
+ return checkPairs(root, pairs, requireSource, onlyChanged);
66
+ }
67
+ const config = loadConfig(root);
68
+ const pairs = getSourceEncryptedPairs(config);
69
+ return checkPairs(root, pairs, requireSource, onlyChanged);
70
+ }
71
+ function checkPairs(root, pairs, requireSource, onlyChanged) {
72
+ const changedFiles = onlyChanged ? getGitChangedFiles(root) : null;
73
+ const results = [];
74
+ for (const { source, encrypted } of pairs) {
75
+ const sourcePath = join(root, source);
76
+ const encryptedPath = join(root, encrypted);
77
+ if (onlyChanged && changedFiles) {
78
+ const isSourceChanged = changedFiles.has(source);
79
+ const isEncryptedChanged = changedFiles.has(encrypted);
80
+ if (!isSourceChanged && !isEncryptedChanged) {
81
+ continue;
82
+ }
83
+ }
84
+ if (!existsSync(sourcePath)) {
85
+ if (requireSource) {
86
+ results.push({
87
+ source,
88
+ encrypted,
89
+ inSync: false,
90
+ added: [],
91
+ removed: [],
92
+ changed: [],
93
+ error: 'SOURCE_MISSING',
94
+ });
95
+ }
96
+ continue;
97
+ }
98
+ if (!existsSync(encryptedPath)) {
99
+ const sourceContent = readFileSync(sourcePath, 'utf-8');
100
+ const sourceVars = parseEnvContent(sourceContent);
101
+ const allKeys = sourceVars.map(v => v.key);
102
+ results.push({
103
+ source,
104
+ encrypted,
105
+ inSync: false,
106
+ added: allKeys,
107
+ removed: [],
108
+ changed: [],
109
+ error: 'ENCRYPTED_MISSING',
110
+ });
111
+ continue;
112
+ }
113
+ try {
114
+ const decryptedContent = sopsDecrypt(encryptedPath);
115
+ const sourceContent = readFileSync(sourcePath, 'utf-8');
116
+ const sourceVars = parseEnvContent(sourceContent);
117
+ const encryptedVars = parseEnvContent(decryptedContent);
118
+ const diff = computeDiff(sourceVars, encryptedVars);
119
+ results.push({
120
+ source,
121
+ encrypted,
122
+ inSync: isInSync(diff),
123
+ added: diff.added,
124
+ removed: diff.removed,
125
+ changed: diff.changed,
126
+ });
127
+ }
128
+ catch (error) {
129
+ const err = error;
130
+ if (err.message.includes('No matching age key')) {
131
+ results.push({
132
+ source,
133
+ encrypted,
134
+ inSync: false,
135
+ added: [],
136
+ removed: [],
137
+ changed: [],
138
+ error: 'DECRYPT_FAILED',
139
+ });
140
+ }
141
+ else {
142
+ throw error;
143
+ }
144
+ }
145
+ }
146
+ const hasError = results.some(r => r.error === 'SOPS_NOT_INSTALLED' || r.error === 'DECRYPT_FAILED');
147
+ const hasDrift = results.some(r => !r.inSync);
148
+ let status;
149
+ if (hasError) {
150
+ status = 'error';
151
+ }
152
+ else if (hasDrift) {
153
+ status = 'drift';
154
+ }
155
+ else {
156
+ status = 'ok';
157
+ }
158
+ return { status, files: results };
159
+ }
160
+ function formatTextOutput(result) {
161
+ const lines = [];
162
+ lines.push('Checking secrets...\n');
163
+ for (const file of result.files) {
164
+ if (file.error === 'SOPS_NOT_INSTALLED') {
165
+ lines.push(pc.red('Error: SOPS is not installed'));
166
+ lines.push(pc.dim('Run: brew install sops'));
167
+ continue;
168
+ }
169
+ if (file.error === 'SOURCE_MISSING') {
170
+ lines.push(pc.yellow(`Warning: ${file.source} not found (--require-source)`));
171
+ continue;
172
+ }
173
+ lines.push(`${file.source} ${pc.dim('->')} ${file.encrypted}`);
174
+ if (file.error === 'ENCRYPTED_MISSING') {
175
+ lines.push(pc.yellow(` Warning: ${file.encrypted} not found`));
176
+ if (file.added.length > 0) {
177
+ lines.push(` ${pc.yellow('All keys need encryption:')} ${file.added.join(', ')}`);
178
+ }
179
+ continue;
180
+ }
181
+ if (file.error === 'DECRYPT_FAILED') {
182
+ lines.push(pc.red(` Error: Failed to decrypt ${file.encrypted}`));
183
+ lines.push(pc.dim(" This usually means your age key doesn't match."));
184
+ lines.push(pc.dim(' Check: ~/.config/sops/age/key.txt'));
185
+ continue;
186
+ }
187
+ if (file.inSync) {
188
+ lines.push(pc.green(' ✓ In sync'));
189
+ }
190
+ else {
191
+ if (file.added.length > 0) {
192
+ lines.push(` ${pc.yellow('Added keys:')} ${file.added.join(', ')}`);
193
+ }
194
+ if (file.removed.length > 0) {
195
+ lines.push(` ${pc.yellow('Removed keys:')} ${file.removed.join(', ')}`);
196
+ }
197
+ if (file.changed.length > 0) {
198
+ lines.push(` ${pc.yellow('Changed keys:')} ${file.changed.join(', ')}`);
199
+ }
200
+ }
201
+ lines.push('');
202
+ }
203
+ const driftCount = result.files.filter(f => !f.inSync && !f.error).length;
204
+ const errorCount = result.files.filter(f => f.error === 'ENCRYPTED_MISSING').length;
205
+ const totalDrift = driftCount + errorCount;
206
+ if (result.status === 'error') {
207
+ const sopsError = result.files.find(f => f.error === 'SOPS_NOT_INSTALLED');
208
+ if (sopsError) {
209
+ return lines.join('\n');
210
+ }
211
+ lines.push(pc.red('✗ Errors occurred during check'));
212
+ }
213
+ else if (totalDrift > 0) {
214
+ lines.push(pc.yellow(`✗ Drift detected in ${totalDrift} file(s)`));
215
+ lines.push(pc.dim('Run: hush encrypt'));
216
+ }
217
+ else if (result.files.length > 0) {
218
+ lines.push(pc.green('✓ All secrets in sync'));
219
+ }
220
+ return lines.join('\n');
221
+ }
222
+ function formatJsonOutput(result) {
223
+ return JSON.stringify(result, null, 2);
224
+ }
225
+ export async function checkCommand(options) {
226
+ const result = await check(options);
227
+ if (!options.quiet) {
228
+ if (options.json) {
229
+ console.log(formatJsonOutput(result));
230
+ }
231
+ else {
232
+ console.log(formatTextOutput(result));
233
+ }
234
+ }
235
+ if (result.status === 'error') {
236
+ const hasSopsError = result.files.some(f => f.error === 'SOPS_NOT_INSTALLED');
237
+ const hasDecryptError = result.files.some(f => f.error === 'DECRYPT_FAILED');
238
+ if (hasSopsError || hasDecryptError) {
239
+ process.exit(3);
240
+ }
241
+ if (result.files.some(f => f.error === 'SOURCE_MISSING')) {
242
+ process.exit(2);
243
+ }
244
+ }
245
+ if (result.status === 'drift' && !options.warn) {
246
+ process.exit(1);
247
+ }
248
+ process.exit(0);
249
+ }
@@ -1,11 +1,3 @@
1
- import type { Environment } from '../types.js';
2
- interface DecryptOptions {
3
- root: string;
4
- env?: Environment;
5
- }
6
- /**
7
- * Decrypt command - decrypt .env.encrypted and generate env files for all packages
8
- */
1
+ import type { DecryptOptions } from '../types.js';
9
2
  export declare function decryptCommand(options: DecryptOptions): Promise<void>;
10
- export {};
11
3
  //# sourceMappingURL=decrypt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/commands/decrypt.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAC;AAEhE,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAqCD;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAwF3E"}
1
+ {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/commands/decrypt.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAU,MAAM,aAAa,CAAC;AAO1D,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2E3E"}