@chriscode/hush 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +39 -26
- package/dist/commands/check.d.ts +3 -3
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +27 -31
- package/dist/commands/decrypt.d.ts +2 -2
- package/dist/commands/decrypt.d.ts.map +1 -1
- package/dist/commands/decrypt.js +52 -55
- package/dist/commands/edit.d.ts +2 -2
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +10 -12
- package/dist/commands/encrypt.d.ts +2 -2
- package/dist/commands/encrypt.d.ts.map +1 -1
- package/dist/commands/encrypt.js +27 -29
- package/dist/commands/expansions.d.ts +2 -2
- package/dist/commands/expansions.d.ts.map +1 -1
- package/dist/commands/expansions.js +46 -44
- package/dist/commands/has.d.ts +2 -2
- package/dist/commands/has.d.ts.map +1 -1
- package/dist/commands/has.js +12 -15
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +92 -100
- package/dist/commands/inspect.d.ts +2 -2
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +14 -16
- package/dist/commands/keys.d.ts +2 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +47 -49
- package/dist/commands/list.d.ts +2 -2
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +11 -14
- package/dist/commands/migrate.d.ts +2 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +38 -37
- package/dist/commands/push.d.ts +2 -2
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +41 -45
- package/dist/commands/resolve.d.ts +2 -2
- package/dist/commands/resolve.d.ts.map +1 -1
- package/dist/commands/resolve.js +25 -28
- package/dist/commands/run.d.ts +2 -2
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +35 -39
- package/dist/commands/set.d.ts +2 -2
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +61 -70
- package/dist/commands/skill.d.ts +2 -2
- package/dist/commands/skill.d.ts.map +1 -1
- package/dist/commands/skill.js +149 -459
- package/dist/commands/status.d.ts +2 -2
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +48 -52
- package/dist/commands/template.d.ts +2 -2
- package/dist/commands/template.d.ts.map +1 -1
- package/dist/commands/template.js +36 -39
- package/dist/commands/trace.d.ts +2 -2
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +16 -19
- package/dist/config/loader.js +3 -3
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +59 -0
- package/dist/core/parse.js +3 -3
- package/dist/core/sops.js +9 -9
- package/dist/core/template.d.ts +2 -2
- package/dist/core/template.d.ts.map +1 -1
- package/dist/core/template.js +11 -12
- package/dist/lib/age.js +9 -9
- package/dist/lib/fs.d.ts +25 -0
- package/dist/lib/fs.d.ts.map +1 -0
- package/dist/lib/fs.js +36 -0
- package/dist/lib/onepassword.d.ts.map +1 -1
- package/dist/lib/onepassword.js +41 -4
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/version-check.js +5 -5
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
+
import { defaultContext } from './context.js';
|
|
4
5
|
import { encryptCommand } from './commands/encrypt.js';
|
|
5
6
|
import { decryptCommand } from './commands/decrypt.js';
|
|
6
7
|
import { editCommand } from './commands/edit.js';
|
|
@@ -35,7 +36,7 @@ ${pc.bold('Commands:')}
|
|
|
35
36
|
init Initialize hush.yaml config
|
|
36
37
|
encrypt Encrypt source .hush files
|
|
37
38
|
run -- <cmd> Run command with secrets in memory (AI-safe)
|
|
38
|
-
set <KEY>
|
|
39
|
+
set [VALUE] <KEY> Set a single secret (AI-safe, prompts if no value)
|
|
39
40
|
edit [file] Edit all secrets in $EDITOR
|
|
40
41
|
list List all variables (shows values)
|
|
41
42
|
inspect List all variables (masked values, AI-safe)
|
|
@@ -99,6 +100,8 @@ ${pc.bold('File Naming (v5+):')}
|
|
|
99
100
|
.hush.development Development secrets (source file)
|
|
100
101
|
.hush.encrypted Encrypted shared secrets (committed)
|
|
101
102
|
.hush.development.encrypted Encrypted dev secrets (committed)
|
|
103
|
+
|
|
104
|
+
Subdirectories support templates (e.g. apps/web/.hush.development)
|
|
102
105
|
|
|
103
106
|
The .env files are reserved for other tools (Wrangler, Metro, etc.).
|
|
104
107
|
|
|
@@ -110,8 +113,9 @@ ${pc.bold('Examples:')}
|
|
|
110
113
|
hush run -e prod -- npm build Run with production secrets
|
|
111
114
|
hush run -t api -- wrangler dev Run filtered for 'api' target (root secrets only)
|
|
112
115
|
cd apps/mobile && hush run -- expo start Run from subdirectory (applies template + target filters)
|
|
113
|
-
hush set DATABASE_URL Set a secret interactively (
|
|
114
|
-
hush set API_KEY
|
|
116
|
+
hush set DATABASE_URL Set a secret interactively (prompts for value)
|
|
117
|
+
hush set "myvalue" API_KEY Set a secret inline (no prompt)
|
|
118
|
+
hush set API_KEY --gui Set secret via GUI dialog (for AI agents)
|
|
115
119
|
hush set API_KEY -e prod Set a production secret
|
|
116
120
|
hush keys setup Pull key from 1Password or verify local
|
|
117
121
|
hush keys generate Generate new key + backup to 1Password
|
|
@@ -165,6 +169,7 @@ function parseArgs(args) {
|
|
|
165
169
|
let vault;
|
|
166
170
|
let file;
|
|
167
171
|
let key;
|
|
172
|
+
let value;
|
|
168
173
|
let target;
|
|
169
174
|
let cmdArgs = [];
|
|
170
175
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -271,8 +276,16 @@ function parseArgs(args) {
|
|
|
271
276
|
}
|
|
272
277
|
continue;
|
|
273
278
|
}
|
|
274
|
-
if (command === 'set' && !arg.startsWith('-')
|
|
275
|
-
key
|
|
279
|
+
if (command === 'set' && !arg.startsWith('-')) {
|
|
280
|
+
if (!key) {
|
|
281
|
+
key = arg;
|
|
282
|
+
}
|
|
283
|
+
else if (!value) {
|
|
284
|
+
// Second positional arg: shift key to value, this arg is the key
|
|
285
|
+
// Syntax: hush set <VALUE> <KEY>
|
|
286
|
+
value = key;
|
|
287
|
+
key = arg;
|
|
288
|
+
}
|
|
276
289
|
continue;
|
|
277
290
|
}
|
|
278
291
|
if (command === 'has' && !arg.startsWith('-') && !key) {
|
|
@@ -292,7 +305,7 @@ function parseArgs(args) {
|
|
|
292
305
|
continue;
|
|
293
306
|
}
|
|
294
307
|
}
|
|
295
|
-
return { command, subcommand, env, envExplicit, root, dryRun, verbose, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, target, cmdArgs };
|
|
308
|
+
return { command, subcommand, env, envExplicit, root, dryRun, verbose, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, value, target, cmdArgs };
|
|
296
309
|
}
|
|
297
310
|
function checkMigrationNeeded(root, command) {
|
|
298
311
|
const skipCommands = ['', 'help', 'version', 'init', 'skill', 'migrate'];
|
|
@@ -328,7 +341,7 @@ async function main() {
|
|
|
328
341
|
printHelp();
|
|
329
342
|
process.exit(0);
|
|
330
343
|
}
|
|
331
|
-
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);
|
|
344
|
+
const { command, subcommand, env, envExplicit, root, dryRun, verbose, quiet, warn, json, onlyChanged, requireSource, allowPlaintext, global, local, force, gui, vault, file, key, value, target, cmdArgs } = parseArgs(args);
|
|
332
345
|
if (command !== 'run' && !json && !quiet) {
|
|
333
346
|
checkForUpdate(VERSION);
|
|
334
347
|
}
|
|
@@ -336,16 +349,16 @@ async function main() {
|
|
|
336
349
|
try {
|
|
337
350
|
switch (command) {
|
|
338
351
|
case 'init':
|
|
339
|
-
await initCommand({ root });
|
|
352
|
+
await initCommand(defaultContext, { root });
|
|
340
353
|
break;
|
|
341
354
|
case 'encrypt':
|
|
342
|
-
await encryptCommand({ root });
|
|
355
|
+
await encryptCommand(defaultContext, { root });
|
|
343
356
|
break;
|
|
344
357
|
case 'decrypt':
|
|
345
|
-
await decryptCommand({ root, env, force });
|
|
358
|
+
await decryptCommand(defaultContext, { root, env, force });
|
|
346
359
|
break;
|
|
347
360
|
case 'run':
|
|
348
|
-
await runCommand({ root, env, target, command: cmdArgs });
|
|
361
|
+
await runCommand(defaultContext, { root, env, target, command: cmdArgs });
|
|
349
362
|
break;
|
|
350
363
|
case 'set': {
|
|
351
364
|
let setFile = 'shared';
|
|
@@ -355,36 +368,36 @@ async function main() {
|
|
|
355
368
|
else if (envExplicit) {
|
|
356
369
|
setFile = env;
|
|
357
370
|
}
|
|
358
|
-
await setCommand({ root, file: setFile, key, gui });
|
|
371
|
+
await setCommand(defaultContext, { root, file: setFile, key, value, gui });
|
|
359
372
|
break;
|
|
360
373
|
}
|
|
361
374
|
case 'edit':
|
|
362
|
-
await editCommand({ root, file });
|
|
375
|
+
await editCommand(defaultContext, { root, file });
|
|
363
376
|
break;
|
|
364
377
|
case 'list':
|
|
365
|
-
await listCommand({ root, env });
|
|
378
|
+
await listCommand(defaultContext, { root, env });
|
|
366
379
|
break;
|
|
367
380
|
case 'inspect':
|
|
368
|
-
await inspectCommand({ root, env });
|
|
381
|
+
await inspectCommand(defaultContext, { root, env });
|
|
369
382
|
break;
|
|
370
383
|
case 'has':
|
|
371
384
|
if (!key) {
|
|
372
385
|
console.error(pc.red('Usage: hush has <KEY>'));
|
|
373
386
|
process.exit(1);
|
|
374
387
|
}
|
|
375
|
-
await hasCommand({ root, env, key, quiet });
|
|
388
|
+
await hasCommand(defaultContext, { root, env, key, quiet });
|
|
376
389
|
break;
|
|
377
390
|
case 'check':
|
|
378
|
-
await checkCommand({ root, warn, json, quiet, onlyChanged, requireSource, allowPlaintext });
|
|
391
|
+
await checkCommand(defaultContext, { root, warn, json, quiet, onlyChanged, requireSource, allowPlaintext });
|
|
379
392
|
break;
|
|
380
393
|
case 'push':
|
|
381
|
-
await pushCommand({ root, dryRun, verbose, target });
|
|
394
|
+
await pushCommand(defaultContext, { root, dryRun, verbose, target });
|
|
382
395
|
break;
|
|
383
396
|
case 'status':
|
|
384
|
-
await statusCommand({ root });
|
|
397
|
+
await statusCommand(defaultContext, { root });
|
|
385
398
|
break;
|
|
386
399
|
case 'skill':
|
|
387
|
-
await skillCommand({ root, global, local });
|
|
400
|
+
await skillCommand(defaultContext, { root, global, local });
|
|
388
401
|
break;
|
|
389
402
|
case 'keys':
|
|
390
403
|
if (!subcommand) {
|
|
@@ -392,7 +405,7 @@ async function main() {
|
|
|
392
405
|
console.error(pc.dim('Commands: setup, generate, pull, push, list'));
|
|
393
406
|
process.exit(1);
|
|
394
407
|
}
|
|
395
|
-
await keysCommand({ root, subcommand, vault, force });
|
|
408
|
+
await keysCommand(defaultContext, { root, subcommand, vault, force });
|
|
396
409
|
break;
|
|
397
410
|
case 'resolve':
|
|
398
411
|
if (!target) {
|
|
@@ -400,7 +413,7 @@ async function main() {
|
|
|
400
413
|
console.error(pc.dim('Example: hush resolve api-workers'));
|
|
401
414
|
process.exit(1);
|
|
402
415
|
}
|
|
403
|
-
await resolveCommand({ root, env, target });
|
|
416
|
+
await resolveCommand(defaultContext, { root, env, target });
|
|
404
417
|
break;
|
|
405
418
|
case 'trace':
|
|
406
419
|
if (!key) {
|
|
@@ -408,16 +421,16 @@ async function main() {
|
|
|
408
421
|
console.error(pc.dim('Example: hush trace DATABASE_URL'));
|
|
409
422
|
process.exit(1);
|
|
410
423
|
}
|
|
411
|
-
await traceCommand({ root, env, key });
|
|
424
|
+
await traceCommand(defaultContext, { root, env, key });
|
|
412
425
|
break;
|
|
413
426
|
case 'template':
|
|
414
|
-
await templateCommand({ root, env });
|
|
427
|
+
await templateCommand(defaultContext, { root, env });
|
|
415
428
|
break;
|
|
416
429
|
case 'expansions':
|
|
417
|
-
await expansionsCommand({ root, env });
|
|
430
|
+
await expansionsCommand(defaultContext, { root, env });
|
|
418
431
|
break;
|
|
419
432
|
case 'migrate':
|
|
420
|
-
await migrateCommand({ root, dryRun });
|
|
433
|
+
await migrateCommand(defaultContext, { root, dryRun });
|
|
421
434
|
break;
|
|
422
435
|
default:
|
|
423
436
|
if (command) {
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1,4 +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>;
|
|
1
|
+
import type { CheckOptions, CheckResult, HushContext } from '../types.js';
|
|
2
|
+
export declare function check(ctx: HushContext, options: CheckOptions): Promise<CheckResult>;
|
|
3
|
+
export declare function checkCommand(ctx: HushContext, options: CheckOptions): Promise<void>;
|
|
4
4
|
//# sourceMappingURL=check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAmB,WAAW,EAAmC,WAAW,EAAE,MAAM,aAAa,CAAC;AAmF5H,wBAAsB,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BzF;AAwMD,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCzF"}
|
package/dist/commands/check.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
1
|
import { join } from 'node:path';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
2
|
import pc from 'picocolors';
|
|
5
|
-
import { loadConfig } from '../config/loader.js';
|
|
6
3
|
import { parseEnvContent } from '../core/parse.js';
|
|
7
|
-
import { decrypt as sopsDecrypt, isSopsInstalled } from '../core/sops.js';
|
|
8
4
|
import { computeDiff, isInSync } from '../lib/diff.js';
|
|
9
5
|
function getSourceEncryptedPairs(config) {
|
|
10
6
|
const pairs = [];
|
|
@@ -31,10 +27,10 @@ function getSourceEncryptedPairs(config) {
|
|
|
31
27
|
}
|
|
32
28
|
return pairs;
|
|
33
29
|
}
|
|
34
|
-
function getGitChangedFiles(root) {
|
|
30
|
+
function getGitChangedFiles(ctx, root) {
|
|
35
31
|
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' });
|
|
32
|
+
const staged = ctx.exec.execSync('git diff --cached --name-only', { cwd: root, encoding: 'utf-8' });
|
|
33
|
+
const unstaged = ctx.exec.execSync('git diff --name-only', { cwd: root, encoding: 'utf-8' });
|
|
38
34
|
const files = [...staged.split('\n'), ...unstaged.split('\n')].filter(Boolean);
|
|
39
35
|
return new Set(files);
|
|
40
36
|
}
|
|
@@ -42,7 +38,7 @@ function getGitChangedFiles(root) {
|
|
|
42
38
|
return new Set();
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
|
-
function findPlaintextEnvFiles(root) {
|
|
41
|
+
function findPlaintextEnvFiles(ctx, root) {
|
|
46
42
|
const results = [];
|
|
47
43
|
// Only warn about .env files (legacy/output files), NOT .hush files (Hush's source files)
|
|
48
44
|
const plaintextPatterns = ['.env', '.env.development', '.env.production', '.env.local', '.env.staging', '.env.test', '.dev.vars'];
|
|
@@ -50,7 +46,7 @@ function findPlaintextEnvFiles(root) {
|
|
|
50
46
|
function scanDir(dir, relativePath = '') {
|
|
51
47
|
let entries;
|
|
52
48
|
try {
|
|
53
|
-
entries = readdirSync(dir);
|
|
49
|
+
entries = ctx.fs.readdirSync(dir);
|
|
54
50
|
}
|
|
55
51
|
catch {
|
|
56
52
|
return;
|
|
@@ -61,7 +57,7 @@ function findPlaintextEnvFiles(root) {
|
|
|
61
57
|
const fullPath = join(dir, entry);
|
|
62
58
|
const relPath = relativePath ? `${relativePath}/${entry}` : entry;
|
|
63
59
|
try {
|
|
64
|
-
if (statSync(fullPath).isDirectory()) {
|
|
60
|
+
if (ctx.fs.statSync(fullPath).isDirectory()) {
|
|
65
61
|
scanDir(fullPath, relPath);
|
|
66
62
|
}
|
|
67
63
|
else if (plaintextPatterns.includes(entry)) {
|
|
@@ -76,9 +72,9 @@ function findPlaintextEnvFiles(root) {
|
|
|
76
72
|
scanDir(root);
|
|
77
73
|
return results;
|
|
78
74
|
}
|
|
79
|
-
export async function check(options) {
|
|
75
|
+
export async function check(ctx, options) {
|
|
80
76
|
const { root, requireSource, onlyChanged, allowPlaintext } = options;
|
|
81
|
-
if (!isSopsInstalled()) {
|
|
77
|
+
if (!ctx.sops.isSopsInstalled()) {
|
|
82
78
|
return {
|
|
83
79
|
status: 'error',
|
|
84
80
|
files: [{
|
|
@@ -92,11 +88,11 @@ export async function check(options) {
|
|
|
92
88
|
}],
|
|
93
89
|
};
|
|
94
90
|
}
|
|
95
|
-
const config = loadConfig(root);
|
|
91
|
+
const config = ctx.config.loadConfig(root);
|
|
96
92
|
const pairs = getSourceEncryptedPairs(config);
|
|
97
|
-
const result = checkPairs(root, pairs, requireSource, onlyChanged);
|
|
93
|
+
const result = checkPairs(ctx, root, pairs, requireSource, onlyChanged);
|
|
98
94
|
if (!allowPlaintext) {
|
|
99
|
-
const plaintextFiles = findPlaintextEnvFiles(root);
|
|
95
|
+
const plaintextFiles = findPlaintextEnvFiles(ctx, root);
|
|
100
96
|
if (plaintextFiles.length > 0) {
|
|
101
97
|
result.plaintextFiles = plaintextFiles;
|
|
102
98
|
result.status = 'plaintext';
|
|
@@ -104,8 +100,8 @@ export async function check(options) {
|
|
|
104
100
|
}
|
|
105
101
|
return result;
|
|
106
102
|
}
|
|
107
|
-
function checkPairs(root, pairs, requireSource, onlyChanged) {
|
|
108
|
-
const changedFiles = onlyChanged ? getGitChangedFiles(root) : null;
|
|
103
|
+
function checkPairs(ctx, root, pairs, requireSource, onlyChanged) {
|
|
104
|
+
const changedFiles = onlyChanged ? getGitChangedFiles(ctx, root) : null;
|
|
109
105
|
const results = [];
|
|
110
106
|
for (const { source, encrypted } of pairs) {
|
|
111
107
|
const sourcePath = join(root, source);
|
|
@@ -117,7 +113,7 @@ function checkPairs(root, pairs, requireSource, onlyChanged) {
|
|
|
117
113
|
continue;
|
|
118
114
|
}
|
|
119
115
|
}
|
|
120
|
-
if (!existsSync(sourcePath)) {
|
|
116
|
+
if (!ctx.fs.existsSync(sourcePath)) {
|
|
121
117
|
if (requireSource) {
|
|
122
118
|
results.push({
|
|
123
119
|
source,
|
|
@@ -131,8 +127,8 @@ function checkPairs(root, pairs, requireSource, onlyChanged) {
|
|
|
131
127
|
}
|
|
132
128
|
continue;
|
|
133
129
|
}
|
|
134
|
-
if (!existsSync(encryptedPath)) {
|
|
135
|
-
const sourceContent = readFileSync(sourcePath, 'utf-8');
|
|
130
|
+
if (!ctx.fs.existsSync(encryptedPath)) {
|
|
131
|
+
const sourceContent = ctx.fs.readFileSync(sourcePath, 'utf-8');
|
|
136
132
|
const sourceVars = parseEnvContent(sourceContent);
|
|
137
133
|
const allKeys = sourceVars.map(v => v.key);
|
|
138
134
|
results.push({
|
|
@@ -147,8 +143,8 @@ function checkPairs(root, pairs, requireSource, onlyChanged) {
|
|
|
147
143
|
continue;
|
|
148
144
|
}
|
|
149
145
|
try {
|
|
150
|
-
const decryptedContent =
|
|
151
|
-
const sourceContent = readFileSync(sourcePath, 'utf-8');
|
|
146
|
+
const decryptedContent = ctx.sops.decrypt(encryptedPath);
|
|
147
|
+
const sourceContent = ctx.fs.readFileSync(sourcePath, 'utf-8');
|
|
152
148
|
const sourceVars = parseEnvContent(sourceContent);
|
|
153
149
|
const encryptedVars = parseEnvContent(decryptedContent);
|
|
154
150
|
const diff = computeDiff(sourceVars, encryptedVars);
|
|
@@ -277,31 +273,31 @@ function formatTextOutput(result) {
|
|
|
277
273
|
function formatJsonOutput(result) {
|
|
278
274
|
return JSON.stringify(result, null, 2);
|
|
279
275
|
}
|
|
280
|
-
export async function checkCommand(options) {
|
|
281
|
-
const result = await check(options);
|
|
276
|
+
export async function checkCommand(ctx, options) {
|
|
277
|
+
const result = await check(ctx, options);
|
|
282
278
|
if (!options.quiet) {
|
|
283
279
|
if (options.json) {
|
|
284
|
-
|
|
280
|
+
ctx.logger.log(formatJsonOutput(result));
|
|
285
281
|
}
|
|
286
282
|
else {
|
|
287
|
-
|
|
283
|
+
ctx.logger.log(formatTextOutput(result));
|
|
288
284
|
}
|
|
289
285
|
}
|
|
290
286
|
if (result.status === 'plaintext' && !options.warn) {
|
|
291
|
-
process.exit(4);
|
|
287
|
+
ctx.process.exit(4);
|
|
292
288
|
}
|
|
293
289
|
if (result.status === 'error') {
|
|
294
290
|
const hasSopsError = result.files.some(f => f.error === 'SOPS_NOT_INSTALLED');
|
|
295
291
|
const hasDecryptError = result.files.some(f => f.error === 'DECRYPT_FAILED');
|
|
296
292
|
if (hasSopsError || hasDecryptError) {
|
|
297
|
-
process.exit(3);
|
|
293
|
+
ctx.process.exit(3);
|
|
298
294
|
}
|
|
299
295
|
if (result.files.some(f => f.error === 'SOURCE_MISSING')) {
|
|
300
|
-
process.exit(2);
|
|
296
|
+
ctx.process.exit(2);
|
|
301
297
|
}
|
|
302
298
|
}
|
|
303
299
|
if (result.status === 'drift' && !options.warn) {
|
|
304
|
-
process.exit(1);
|
|
300
|
+
ctx.process.exit(1);
|
|
305
301
|
}
|
|
306
|
-
process.exit(0);
|
|
302
|
+
ctx.process.exit(0);
|
|
307
303
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { DecryptOptions } from '../types.js';
|
|
2
|
-
export declare function decryptCommand(options: DecryptOptions): Promise<void>;
|
|
1
|
+
import type { DecryptOptions, HushContext } from '../types.js';
|
|
2
|
+
export declare function decryptCommand(ctx: HushContext, options: DecryptOptions): Promise<void>;
|
|
3
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":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAmDvE,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA8F7F"}
|
package/dist/commands/decrypt.js
CHANGED
|
@@ -1,60 +1,57 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import pc from 'picocolors';
|
|
5
|
-
import { loadConfig } from '../config/loader.js';
|
|
6
4
|
import { filterVarsForTarget } from '../core/filter.js';
|
|
7
5
|
import { interpolateVars, getUnresolvedVars } from '../core/interpolate.js';
|
|
8
6
|
import { mergeVars } from '../core/merge.js';
|
|
9
7
|
import { parseEnvContent, parseEnvFile } from '../core/parse.js';
|
|
10
|
-
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
11
8
|
import { formatVars } from '../formats/index.js';
|
|
12
9
|
import { FORMAT_OUTPUT_FILES } from '../types.js';
|
|
13
10
|
function getEncryptedPath(sourcePath) {
|
|
14
11
|
return sourcePath + '.encrypted';
|
|
15
12
|
}
|
|
16
|
-
async function confirmDangerousOperation() {
|
|
17
|
-
if (!process.stdin.isTTY) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
async function confirmDangerousOperation(ctx) {
|
|
14
|
+
if (!ctx.process.stdin.isTTY) {
|
|
15
|
+
ctx.logger.error('\nError: decrypt --force requires interactive confirmation.');
|
|
16
|
+
ctx.logger.error('This command cannot be run in non-interactive environments.');
|
|
17
|
+
ctx.logger.error('\nUse "hush run -- <command>" instead to inject secrets into memory.');
|
|
21
18
|
return false;
|
|
22
19
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
ctx.logger.log('');
|
|
21
|
+
ctx.logger.log(pc.red('━'.repeat(70)));
|
|
22
|
+
ctx.logger.log(pc.red(pc.bold(' ⚠️ WARNING: WRITING PLAINTEXT SECRETS TO DISK')));
|
|
23
|
+
ctx.logger.log(pc.red('━'.repeat(70)));
|
|
24
|
+
ctx.logger.log('');
|
|
25
|
+
ctx.logger.log(pc.yellow(' This will create unencrypted .env files that:'));
|
|
26
|
+
ctx.logger.log(pc.dim(' • Can be read by AI assistants, scripts, and other tools'));
|
|
27
|
+
ctx.logger.log(pc.dim(' • May accidentally be committed to git'));
|
|
28
|
+
ctx.logger.log(pc.dim(' • Defeat the "encrypted at rest" security model'));
|
|
29
|
+
ctx.logger.log('');
|
|
30
|
+
ctx.logger.log(pc.green(' Recommended alternative:'));
|
|
31
|
+
ctx.logger.log(pc.cyan(' hush run -- <command>'));
|
|
32
|
+
ctx.logger.log(pc.dim(' Decrypts to memory only, secrets never touch disk.'));
|
|
33
|
+
ctx.logger.log('');
|
|
34
|
+
ctx.logger.log(pc.red('━'.repeat(70)));
|
|
35
|
+
ctx.logger.log('');
|
|
39
36
|
const rl = createInterface({
|
|
40
|
-
input: process.stdin,
|
|
41
|
-
output: process.stdout,
|
|
37
|
+
input: ctx.process.stdin,
|
|
38
|
+
output: ctx.process.stdout,
|
|
42
39
|
});
|
|
43
40
|
return new Promise((resolve) => {
|
|
44
41
|
rl.question(`${pc.bold('Type "yes" to proceed:')} `, (answer) => {
|
|
45
42
|
rl.close();
|
|
46
43
|
if (answer.toLowerCase() === 'yes') {
|
|
47
|
-
|
|
44
|
+
ctx.logger.log('');
|
|
48
45
|
resolve(true);
|
|
49
46
|
}
|
|
50
47
|
else {
|
|
51
|
-
|
|
48
|
+
ctx.logger.log(pc.dim('\nAborted. No files were written.'));
|
|
52
49
|
resolve(false);
|
|
53
50
|
}
|
|
54
51
|
});
|
|
55
52
|
});
|
|
56
53
|
}
|
|
57
|
-
export async function decryptCommand(options) {
|
|
54
|
+
export async function decryptCommand(ctx, options) {
|
|
58
55
|
const { root, env, force } = options;
|
|
59
56
|
if (!force) {
|
|
60
57
|
console.error(pc.red('Error: decrypt requires --force flag'));
|
|
@@ -66,64 +63,64 @@ export async function decryptCommand(options) {
|
|
|
66
63
|
console.error(pc.cyan(' hush decrypt --force'));
|
|
67
64
|
process.exit(1);
|
|
68
65
|
}
|
|
69
|
-
const confirmed = await confirmDangerousOperation();
|
|
66
|
+
const confirmed = await confirmDangerousOperation(ctx);
|
|
70
67
|
if (!confirmed) {
|
|
71
|
-
process.exit(0);
|
|
68
|
+
ctx.process.exit(0);
|
|
72
69
|
}
|
|
73
|
-
const config = loadConfig(root);
|
|
74
|
-
|
|
70
|
+
const config = ctx.config.loadConfig(root);
|
|
71
|
+
ctx.logger.log(pc.yellow(`⚠️ Writing unencrypted secrets for ${env}...`));
|
|
75
72
|
const sharedEncrypted = join(root, getEncryptedPath(config.sources.shared));
|
|
76
73
|
const envEncrypted = join(root, getEncryptedPath(config.sources[env]));
|
|
77
|
-
const localPath = join(root,
|
|
74
|
+
const localPath = join(root, config.sources.local);
|
|
78
75
|
const varSources = [];
|
|
79
|
-
if (existsSync(sharedEncrypted)) {
|
|
80
|
-
const content =
|
|
76
|
+
if (ctx.fs.existsSync(sharedEncrypted)) {
|
|
77
|
+
const content = ctx.sops.decrypt(sharedEncrypted);
|
|
81
78
|
const vars = parseEnvContent(content);
|
|
82
79
|
varSources.push(vars);
|
|
83
|
-
|
|
80
|
+
ctx.logger.log(pc.dim(` ${config.sources.shared}.encrypted: ${vars.length} vars`));
|
|
84
81
|
}
|
|
85
|
-
if (existsSync(envEncrypted)) {
|
|
86
|
-
const content =
|
|
82
|
+
if (ctx.fs.existsSync(envEncrypted)) {
|
|
83
|
+
const content = ctx.sops.decrypt(envEncrypted);
|
|
87
84
|
const vars = parseEnvContent(content);
|
|
88
85
|
varSources.push(vars);
|
|
89
|
-
|
|
86
|
+
ctx.logger.log(pc.dim(` ${config.sources[env]}.encrypted: ${vars.length} vars`));
|
|
90
87
|
}
|
|
91
|
-
if (existsSync(localPath)) {
|
|
88
|
+
if (ctx.fs.existsSync(localPath)) {
|
|
92
89
|
const vars = parseEnvFile(localPath);
|
|
93
90
|
varSources.push(vars);
|
|
94
|
-
|
|
91
|
+
ctx.logger.log(pc.dim(` ${config.sources.local}: ${vars.length} vars (overrides)`));
|
|
95
92
|
}
|
|
96
93
|
if (varSources.length === 0) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
process.exit(1);
|
|
94
|
+
ctx.logger.error(pc.red('No encrypted files found'));
|
|
95
|
+
ctx.logger.error(pc.dim(`Expected: ${sharedEncrypted}`));
|
|
96
|
+
ctx.process.exit(1);
|
|
100
97
|
}
|
|
101
98
|
const merged = mergeVars(...varSources);
|
|
102
99
|
const interpolated = interpolateVars(merged);
|
|
103
100
|
const unresolved = getUnresolvedVars(interpolated);
|
|
104
101
|
if (unresolved.length > 0) {
|
|
105
|
-
|
|
102
|
+
ctx.logger.warn(pc.yellow(` Warning: ${unresolved.length} vars have unresolved references`));
|
|
106
103
|
}
|
|
107
|
-
|
|
104
|
+
ctx.logger.log(pc.yellow(`\n⚠️ Writing to ${config.targets.length} targets:`));
|
|
108
105
|
for (const target of config.targets) {
|
|
109
106
|
const targetDir = join(root, target.path);
|
|
110
107
|
const filtered = filterVarsForTarget(interpolated, target);
|
|
111
108
|
if (filtered.length === 0) {
|
|
112
|
-
|
|
109
|
+
ctx.logger.log(pc.dim(` ${target.path}/ - no matching vars, skipped`));
|
|
113
110
|
continue;
|
|
114
111
|
}
|
|
115
112
|
const outputFilename = FORMAT_OUTPUT_FILES[target.format][env];
|
|
116
113
|
const outputPath = join(targetDir, outputFilename);
|
|
117
|
-
if (!existsSync(targetDir)) {
|
|
118
|
-
mkdirSync(targetDir, { recursive: true });
|
|
114
|
+
if (!ctx.fs.existsSync(targetDir)) {
|
|
115
|
+
ctx.fs.mkdirSync(targetDir, { recursive: true });
|
|
119
116
|
}
|
|
120
117
|
const content = formatVars(filtered, target.format);
|
|
121
|
-
writeFileSync(outputPath, content, 'utf-8');
|
|
118
|
+
ctx.fs.writeFileSync(outputPath, content, 'utf-8');
|
|
122
119
|
const relativePath = target.path === '.' ? outputFilename : `${target.path}/${outputFilename}`;
|
|
123
|
-
|
|
120
|
+
ctx.logger.log(pc.yellow(` ⚠️ ${relativePath}`) +
|
|
124
121
|
pc.dim(` (${target.format}, ${filtered.length} vars)`));
|
|
125
122
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
ctx.logger.log('');
|
|
124
|
+
ctx.logger.log(pc.yellow('⚠️ Decryption complete - plaintext secrets on disk'));
|
|
125
|
+
ctx.logger.log(pc.dim(' Delete these files when done, or use "hush run" next time.'));
|
|
129
126
|
}
|
package/dist/commands/edit.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { EditOptions } from '../types.js';
|
|
2
|
-
export declare function editCommand(options: EditOptions): Promise<void>;
|
|
1
|
+
import type { EditOptions, HushContext } from '../types.js';
|
|
2
|
+
export declare function editCommand(ctx: HushContext, options: EditOptions): Promise<void>;
|
|
3
3
|
//# sourceMappingURL=edit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/commands/edit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/commands/edit.ts"],"names":[],"mappings":"AAGC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI7D,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvF"}
|
package/dist/commands/edit.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
1
|
import { join } from 'node:path';
|
|
3
2
|
import pc from 'picocolors';
|
|
4
|
-
import { loadConfig } from '../config/loader.js';
|
|
5
3
|
import { edit as sopsEdit } from '../core/sops.js';
|
|
6
|
-
export async function editCommand(options) {
|
|
4
|
+
export async function editCommand(ctx, options) {
|
|
7
5
|
const { root, file } = options;
|
|
8
|
-
const config = loadConfig(root);
|
|
6
|
+
const config = ctx.config.loadConfig(root);
|
|
9
7
|
const fileKey = file ?? 'shared';
|
|
10
8
|
const sourcePath = config.sources[fileKey];
|
|
11
9
|
const encryptedPath = join(root, sourcePath + '.encrypted');
|
|
12
|
-
if (!existsSync(encryptedPath)) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
process.exit(1);
|
|
10
|
+
if (!ctx.fs.existsSync(encryptedPath)) {
|
|
11
|
+
ctx.logger.error(pc.red(`Encrypted file not found: ${sourcePath}.encrypted`));
|
|
12
|
+
ctx.logger.error(pc.dim('Run "hush encrypt" first to create encrypted files'));
|
|
13
|
+
ctx.process.exit(1);
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
ctx.logger.log(pc.blue(`Editing ${sourcePath}.encrypted...`));
|
|
16
|
+
ctx.logger.log(pc.dim('Changes will be encrypted on save'));
|
|
19
17
|
sopsEdit(encryptedPath);
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
ctx.logger.log(pc.green('\nEdit complete'));
|
|
19
|
+
ctx.logger.log(pc.dim('Run "hush run -- <command>" to use updated secrets'));
|
|
22
20
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { EncryptOptions } from '../types.js';
|
|
2
|
-
export declare function encryptCommand(options: EncryptOptions): Promise<void>;
|
|
1
|
+
import type { EncryptOptions, HushContext } from '../types.js';
|
|
2
|
+
export declare function encryptCommand(ctx: HushContext, options: EncryptOptions): Promise<void>;
|
|
3
3
|
//# sourceMappingURL=encrypt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/commands/encrypt.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/commands/encrypt.ts"],"names":[],"mappings":"AAIC,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAShE,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAsF7F"}
|