@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.
- package/dist/cli.js +189 -74
- package/dist/commands/check.d.ts +4 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +249 -0
- package/dist/commands/decrypt.d.ts +1 -9
- package/dist/commands/decrypt.d.ts.map +1 -1
- package/dist/commands/decrypt.js +54 -88
- package/dist/commands/edit.d.ts +1 -7
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +13 -19
- package/dist/commands/encrypt.d.ts +1 -7
- package/dist/commands/encrypt.d.ts.map +1 -1
- package/dist/commands/encrypt.js +23 -19
- package/dist/commands/has.d.ts +9 -0
- package/dist/commands/has.d.ts.map +1 -0
- package/dist/commands/has.js +45 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +63 -0
- package/dist/commands/inspect.d.ts +7 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +50 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +35 -0
- package/dist/commands/push.d.ts +1 -8
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +45 -63
- package/dist/commands/status.d.ts +1 -7
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +43 -45
- package/dist/config/loader.d.ts +5 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +50 -0
- package/dist/core/filter.d.ts +4 -0
- package/dist/core/filter.d.ts.map +1 -0
- package/dist/core/filter.js +27 -0
- package/dist/core/interpolate.d.ts +6 -0
- package/dist/core/interpolate.d.ts.map +1 -0
- package/dist/core/interpolate.js +43 -0
- package/dist/core/mask.d.ts +11 -0
- package/dist/core/mask.d.ts.map +1 -0
- package/dist/core/mask.js +30 -0
- package/dist/core/merge.d.ts +3 -0
- package/dist/core/merge.d.ts.map +1 -0
- package/dist/core/merge.js +9 -0
- package/dist/core/parse.d.ts +3 -27
- package/dist/core/parse.d.ts.map +1 -1
- package/dist/core/parse.js +8 -77
- package/dist/core/sops.d.ts +1 -12
- package/dist/core/sops.d.ts.map +1 -1
- package/dist/core/sops.js +4 -25
- package/dist/formats/dotenv.d.ts +3 -0
- package/dist/formats/dotenv.d.ts.map +1 -0
- package/dist/formats/dotenv.js +3 -0
- package/dist/formats/index.d.ts +8 -0
- package/dist/formats/index.d.ts.map +1 -0
- package/dist/formats/index.js +17 -0
- package/dist/formats/json.d.ts +3 -0
- package/dist/formats/json.d.ts.map +1 -0
- package/dist/formats/json.js +7 -0
- package/dist/formats/shell.d.ts +3 -0
- package/dist/formats/shell.d.ts.map +1 -0
- package/dist/formats/shell.js +11 -0
- package/dist/formats/wrangler.d.ts +3 -0
- package/dist/formats/wrangler.d.ts.map +1 -0
- package/dist/formats/wrangler.js +3 -0
- package/dist/index.d.ts +17 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -9
- package/dist/lib/diff.d.ts +12 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +19 -0
- package/dist/types.d.ts +65 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -6
- package/package.json +15 -14
- package/README.md +0 -194
- package/dist/core/discover.d.ts +0 -6
- package/dist/core/discover.d.ts.map +0 -1
- 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 {
|
|
5
|
+
import { editCommand } from './commands/edit.js';
|
|
8
6
|
import { statusCommand } from './commands/status.js';
|
|
9
|
-
|
|
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
|
-
|
|
17
|
-
encrypt
|
|
18
|
-
|
|
19
|
-
edit
|
|
20
|
-
|
|
21
|
-
|
|
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 <
|
|
25
|
-
|
|
26
|
-
--
|
|
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
|
|
30
|
-
hush
|
|
31
|
-
hush
|
|
32
|
-
hush
|
|
33
|
-
hush
|
|
34
|
-
hush edit
|
|
35
|
-
hush
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 === '
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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: ${
|
|
53
|
-
console.error(pc.dim('
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
126
|
+
if (arg === '--warn') {
|
|
127
|
+
warn = true;
|
|
128
|
+
continue;
|
|
60
129
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
130
|
+
if (arg === '--json') {
|
|
131
|
+
json = true;
|
|
132
|
+
continue;
|
|
64
133
|
}
|
|
65
|
-
|
|
66
|
-
|
|
134
|
+
if (arg === '--only-changed') {
|
|
135
|
+
onlyChanged = true;
|
|
136
|
+
continue;
|
|
67
137
|
}
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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()
|
|
105
|
-
console.error(pc.red('Fatal error:'), error.message);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
});
|
|
222
|
+
main();
|
|
@@ -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 {
|
|
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":"
|
|
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"}
|