@chriscode/hush 3.0.0 → 3.1.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 +7 -0
- package/dist/commands/decrypt.d.ts +3 -0
- package/dist/commands/decrypt.d.ts.map +1 -0
- package/dist/commands/decrypt.js +129 -0
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +93 -17
- package/dist/commands/skill.d.ts.map +1 -1
- package/dist/commands/skill.js +1 -0
- package/dist/core/sops.d.ts.map +1 -1
- package/dist/core/sops.js +10 -13
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import { encryptCommand } from './commands/encrypt.js';
|
|
5
|
+
import { decryptCommand } from './commands/decrypt.js';
|
|
5
6
|
import { editCommand } from './commands/edit.js';
|
|
6
7
|
import { setCommand } from './commands/set.js';
|
|
7
8
|
import { runCommand } from './commands/run.js';
|
|
@@ -46,6 +47,9 @@ ${pc.bold('Debugging Commands:')}
|
|
|
46
47
|
resolve <target> Show what variables a target receives (AI-safe)
|
|
47
48
|
trace <key> Trace a variable through sources and targets (AI-safe)
|
|
48
49
|
|
|
50
|
+
${pc.bold('Advanced Commands:')}
|
|
51
|
+
decrypt --force Write secrets to disk (requires confirmation, last resort)
|
|
52
|
+
|
|
49
53
|
${pc.bold('Options:')}
|
|
50
54
|
-e, --env <env> Environment: development or production (default: development)
|
|
51
55
|
-r, --root <dir> Root directory (default: current directory)
|
|
@@ -300,6 +304,9 @@ async function main() {
|
|
|
300
304
|
case 'encrypt':
|
|
301
305
|
await encryptCommand({ root });
|
|
302
306
|
break;
|
|
307
|
+
case 'decrypt':
|
|
308
|
+
await decryptCommand({ root, env, force });
|
|
309
|
+
break;
|
|
303
310
|
case 'run':
|
|
304
311
|
await runCommand({ root, env, target, command: cmdArgs });
|
|
305
312
|
break;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/commands/decrypt.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAU,MAAM,aAAa,CAAC;AAmD1D,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA8F3E"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig } from '../config/loader.js';
|
|
6
|
+
import { filterVarsForTarget } from '../core/filter.js';
|
|
7
|
+
import { interpolateVars, getUnresolvedVars } from '../core/interpolate.js';
|
|
8
|
+
import { mergeVars } from '../core/merge.js';
|
|
9
|
+
import { parseEnvContent, parseEnvFile } from '../core/parse.js';
|
|
10
|
+
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
11
|
+
import { formatVars } from '../formats/index.js';
|
|
12
|
+
import { FORMAT_OUTPUT_FILES } from '../types.js';
|
|
13
|
+
function getEncryptedPath(sourcePath) {
|
|
14
|
+
return sourcePath + '.encrypted';
|
|
15
|
+
}
|
|
16
|
+
async function confirmDangerousOperation() {
|
|
17
|
+
if (!process.stdin.isTTY) {
|
|
18
|
+
console.error(pc.red('\nError: decrypt --force requires interactive confirmation.'));
|
|
19
|
+
console.error(pc.dim('This command cannot be run in non-interactive environments.'));
|
|
20
|
+
console.error(pc.dim('\nUse "hush run -- <command>" instead to inject secrets into memory.'));
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
console.log('');
|
|
24
|
+
console.log(pc.red('━'.repeat(70)));
|
|
25
|
+
console.log(pc.red(pc.bold(' ⚠️ WARNING: WRITING PLAINTEXT SECRETS TO DISK')));
|
|
26
|
+
console.log(pc.red('━'.repeat(70)));
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(pc.yellow(' This will create unencrypted .env files that:'));
|
|
29
|
+
console.log(pc.dim(' • Can be read by AI assistants, scripts, and other tools'));
|
|
30
|
+
console.log(pc.dim(' • May accidentally be committed to git'));
|
|
31
|
+
console.log(pc.dim(' • Defeat the "encrypted at rest" security model'));
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(pc.green(' Recommended alternative:'));
|
|
34
|
+
console.log(pc.cyan(' hush run -- <your-command>'));
|
|
35
|
+
console.log(pc.dim(' Decrypts to memory only, secrets never touch disk.'));
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(pc.red('━'.repeat(70)));
|
|
38
|
+
console.log('');
|
|
39
|
+
const rl = createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output: process.stdout,
|
|
42
|
+
});
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
rl.question(`${pc.bold('Type "yes" to proceed:')} `, (answer) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
if (answer.toLowerCase() === 'yes') {
|
|
47
|
+
console.log('');
|
|
48
|
+
resolve(true);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(pc.dim('\nAborted. No files were written.'));
|
|
52
|
+
resolve(false);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export async function decryptCommand(options) {
|
|
58
|
+
const { root, env, force } = options;
|
|
59
|
+
if (!force) {
|
|
60
|
+
console.error(pc.red('Error: decrypt requires --force flag'));
|
|
61
|
+
console.error('');
|
|
62
|
+
console.error(pc.dim('This command writes plaintext secrets to disk, which is generally unsafe.'));
|
|
63
|
+
console.error(pc.dim('Use "hush run -- <command>" instead for memory-only decryption.'));
|
|
64
|
+
console.error('');
|
|
65
|
+
console.error(pc.dim('If you really need plaintext files:'));
|
|
66
|
+
console.error(pc.cyan(' hush decrypt --force'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const confirmed = await confirmDangerousOperation();
|
|
70
|
+
if (!confirmed) {
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
const config = loadConfig(root);
|
|
74
|
+
console.log(pc.yellow(`⚠️ Writing unencrypted secrets for ${env}...`));
|
|
75
|
+
const sharedEncrypted = join(root, getEncryptedPath(config.sources.shared));
|
|
76
|
+
const envEncrypted = join(root, getEncryptedPath(config.sources[env]));
|
|
77
|
+
const localPath = join(root, '.env.local');
|
|
78
|
+
const varSources = [];
|
|
79
|
+
if (existsSync(sharedEncrypted)) {
|
|
80
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
81
|
+
const vars = parseEnvContent(content);
|
|
82
|
+
varSources.push(vars);
|
|
83
|
+
console.log(pc.dim(` ${config.sources.shared}.encrypted: ${vars.length} vars`));
|
|
84
|
+
}
|
|
85
|
+
if (existsSync(envEncrypted)) {
|
|
86
|
+
const content = sopsDecrypt(envEncrypted);
|
|
87
|
+
const vars = parseEnvContent(content);
|
|
88
|
+
varSources.push(vars);
|
|
89
|
+
console.log(pc.dim(` ${config.sources[env]}.encrypted: ${vars.length} vars`));
|
|
90
|
+
}
|
|
91
|
+
if (existsSync(localPath)) {
|
|
92
|
+
const vars = parseEnvFile(localPath);
|
|
93
|
+
varSources.push(vars);
|
|
94
|
+
console.log(pc.dim(` .env.local: ${vars.length} vars (overrides)`));
|
|
95
|
+
}
|
|
96
|
+
if (varSources.length === 0) {
|
|
97
|
+
console.error(pc.red('No encrypted files found'));
|
|
98
|
+
console.error(pc.dim(`Expected: ${sharedEncrypted}`));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const merged = mergeVars(...varSources);
|
|
102
|
+
const interpolated = interpolateVars(merged);
|
|
103
|
+
const unresolved = getUnresolvedVars(interpolated);
|
|
104
|
+
if (unresolved.length > 0) {
|
|
105
|
+
console.warn(pc.yellow(` Warning: ${unresolved.length} vars have unresolved references`));
|
|
106
|
+
}
|
|
107
|
+
console.log(pc.yellow(`\n⚠️ Writing to ${config.targets.length} targets:`));
|
|
108
|
+
for (const target of config.targets) {
|
|
109
|
+
const targetDir = join(root, target.path);
|
|
110
|
+
const filtered = filterVarsForTarget(interpolated, target);
|
|
111
|
+
if (filtered.length === 0) {
|
|
112
|
+
console.log(pc.dim(` ${target.path}/ - no matching vars, skipped`));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const outputFilename = FORMAT_OUTPUT_FILES[target.format][env];
|
|
116
|
+
const outputPath = join(targetDir, outputFilename);
|
|
117
|
+
if (!existsSync(targetDir)) {
|
|
118
|
+
mkdirSync(targetDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
const content = formatVars(filtered, target.format);
|
|
121
|
+
writeFileSync(outputPath, content, 'utf-8');
|
|
122
|
+
const relativePath = target.path === '.' ? outputFilename : `${target.path}/${outputFilename}`;
|
|
123
|
+
console.log(pc.yellow(` ⚠️ ${relativePath}`) +
|
|
124
|
+
pc.dim(` (${target.format}, ${filtered.length} vars)`));
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(pc.yellow('⚠️ Decryption complete - plaintext secrets on disk'));
|
|
128
|
+
console.log(pc.dim(' Delete these files when done, or use "hush run" next time.'));
|
|
129
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../src/commands/set.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../src/commands/set.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwK9C,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CnE"}
|
package/dist/commands/set.js
CHANGED
|
@@ -18,6 +18,79 @@ function promptViaMacOSDialog(key) {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
function promptViaWindowsDialog(key) {
|
|
22
|
+
try {
|
|
23
|
+
const psScript = `
|
|
24
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
25
|
+
Add-Type -AssemblyName System.Drawing
|
|
26
|
+
|
|
27
|
+
$form = New-Object System.Windows.Forms.Form
|
|
28
|
+
$form.Text = 'Hush - Set Secret'
|
|
29
|
+
$form.Size = New-Object System.Drawing.Size(300,150)
|
|
30
|
+
$form.StartPosition = 'CenterScreen'
|
|
31
|
+
|
|
32
|
+
$label = New-Object System.Windows.Forms.Label
|
|
33
|
+
$label.Location = New-Object System.Drawing.Point(10,20)
|
|
34
|
+
$label.Size = New-Object System.Drawing.Size(280,20)
|
|
35
|
+
$label.Text = 'Enter value for ${key}:'
|
|
36
|
+
$form.Controls.Add($label)
|
|
37
|
+
|
|
38
|
+
$textBox = New-Object System.Windows.Forms.TextBox
|
|
39
|
+
$textBox.Location = New-Object System.Drawing.Point(10,50)
|
|
40
|
+
$textBox.Size = New-Object System.Drawing.Size(260,20)
|
|
41
|
+
$textBox.PasswordChar = '*'
|
|
42
|
+
$form.Controls.Add($textBox)
|
|
43
|
+
|
|
44
|
+
$okButton = New-Object System.Windows.Forms.Button
|
|
45
|
+
$okButton.Location = New-Object System.Drawing.Point(10,80)
|
|
46
|
+
$okButton.Size = New-Object System.Drawing.Size(75,23)
|
|
47
|
+
$okButton.Text = 'OK'
|
|
48
|
+
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
|
|
49
|
+
$form.AcceptButton = $okButton
|
|
50
|
+
$form.Controls.Add($okButton)
|
|
51
|
+
|
|
52
|
+
$form.TopMost = $true
|
|
53
|
+
|
|
54
|
+
$result = $form.ShowDialog()
|
|
55
|
+
|
|
56
|
+
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
57
|
+
Write-Output $textBox.Text
|
|
58
|
+
} else {
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const encodedCommand = Buffer.from(psScript, 'utf16le').toString('base64');
|
|
63
|
+
const result = execSync(`powershell -EncodedCommand "${encodedCommand}"`, {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
});
|
|
67
|
+
return result.trim();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function promptViaLinuxDialog(key) {
|
|
74
|
+
try {
|
|
75
|
+
const result = execSync(`zenity --password --title="Hush - Set Secret" --text="Enter value for ${key}:"`, {
|
|
76
|
+
encoding: 'utf-8',
|
|
77
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
78
|
+
});
|
|
79
|
+
return result.trim();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
try {
|
|
83
|
+
const result = execSync(`kdialog --password "Enter value for ${key}:" --title "Hush - Set Secret"`, {
|
|
84
|
+
encoding: 'utf-8',
|
|
85
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
86
|
+
});
|
|
87
|
+
return result.trim();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
21
94
|
function promptViaTTY(key) {
|
|
22
95
|
return new Promise((resolve, reject) => {
|
|
23
96
|
process.stdout.write(`Enter value for ${pc.cyan(key)}: `);
|
|
@@ -60,26 +133,29 @@ function promptViaTTY(key) {
|
|
|
60
133
|
});
|
|
61
134
|
}
|
|
62
135
|
async function promptForValue(key, forceGui) {
|
|
63
|
-
if (
|
|
64
|
-
console.log(pc.dim('Opening dialog for secret input...'));
|
|
65
|
-
const value = promptViaMacOSDialog(key);
|
|
66
|
-
if (value !== null) {
|
|
67
|
-
return value;
|
|
68
|
-
}
|
|
69
|
-
throw new Error('Dialog cancelled or failed');
|
|
70
|
-
}
|
|
71
|
-
if (process.stdin.isTTY) {
|
|
136
|
+
if (process.stdin.isTTY && !forceGui) {
|
|
72
137
|
return promptViaTTY(key);
|
|
73
138
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
139
|
+
console.log(pc.dim('Opening dialog for secret input...'));
|
|
140
|
+
let value = null;
|
|
141
|
+
switch (platform()) {
|
|
142
|
+
case 'darwin':
|
|
143
|
+
value = promptViaMacOSDialog(key);
|
|
144
|
+
break;
|
|
145
|
+
case 'win32':
|
|
146
|
+
value = promptViaWindowsDialog(key);
|
|
147
|
+
break;
|
|
148
|
+
case 'linux':
|
|
149
|
+
value = promptViaLinuxDialog(key);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (value !== null) {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
if (platform() === 'linux') {
|
|
156
|
+
throw new Error('GUI prompt failed. Please install "zenity" or "kdialog".');
|
|
81
157
|
}
|
|
82
|
-
throw new Error('Interactive input requires a terminal (TTY) or
|
|
158
|
+
throw new Error('Dialog cancelled or failed. Interactive input requires a terminal (TTY) or a supported GUI environment.');
|
|
83
159
|
}
|
|
84
160
|
export async function setCommand(options) {
|
|
85
161
|
const { root, file, key, gui } = options;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAopChD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvE"}
|
package/dist/commands/skill.js
CHANGED
|
@@ -102,6 +102,7 @@ npx hush run -e production -- npm build # Production
|
|
|
102
102
|
### Commands to AVOID:
|
|
103
103
|
- \`cat .env\` - Never read plaintext .env files directly
|
|
104
104
|
- \`hush list\` - Shows actual secret values (use \`hush inspect\` instead)
|
|
105
|
+
- \`hush decrypt --force\` - Writes plaintext to disk (use \`hush run\` instead)
|
|
105
106
|
|
|
106
107
|
---
|
|
107
108
|
|
package/dist/core/sops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"AA0BA,wBAAgB,eAAe,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"AA0BA,wBAAgB,eAAe,IAAI,OAAO,CAUzC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6BhD;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAsBnE;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAsB3C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA+CzE"}
|
package/dist/core/sops.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { execSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
4
|
+
import { tmpdir, homedir } from 'node:os';
|
|
5
5
|
function getAgeKeyFile() {
|
|
6
6
|
if (process.env.SOPS_AGE_KEY_FILE) {
|
|
7
7
|
return process.env.SOPS_AGE_KEY_FILE;
|
|
8
8
|
}
|
|
9
|
-
const defaultPath = join(
|
|
9
|
+
const defaultPath = join(homedir(), '.config', 'sops', 'age', 'key.txt');
|
|
10
10
|
if (existsSync(defaultPath)) {
|
|
11
11
|
return defaultPath;
|
|
12
12
|
}
|
|
@@ -21,8 +21,11 @@ function getSopsEnv() {
|
|
|
21
21
|
}
|
|
22
22
|
export function isSopsInstalled() {
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const result = spawnSync('sops', ['--version'], {
|
|
25
|
+
stdio: 'ignore',
|
|
26
|
+
shell: true
|
|
27
|
+
});
|
|
28
|
+
return result.status === 0;
|
|
26
29
|
}
|
|
27
30
|
catch {
|
|
28
31
|
return false;
|
|
@@ -36,7 +39,7 @@ export function decrypt(filePath) {
|
|
|
36
39
|
throw new Error(`Encrypted file not found: ${filePath}`);
|
|
37
40
|
}
|
|
38
41
|
if (!isSopsInstalled()) {
|
|
39
|
-
throw new Error('SOPS is not installed. Install with: brew install sops');
|
|
42
|
+
throw new Error('SOPS is not installed. Install with: brew install sops (Mac) or scoop install sops (Windows)');
|
|
40
43
|
}
|
|
41
44
|
try {
|
|
42
45
|
const result = execSync(`sops --input-type dotenv --output-type dotenv --decrypt "${filePath}"`, {
|
|
@@ -60,12 +63,11 @@ export function encrypt(inputPath, outputPath) {
|
|
|
60
63
|
throw new Error(`Input file not found: ${inputPath}`);
|
|
61
64
|
}
|
|
62
65
|
if (!isSopsInstalled()) {
|
|
63
|
-
throw new Error('SOPS is not installed. Install with: brew install sops');
|
|
66
|
+
throw new Error('SOPS is not installed. Install with: brew install sops (Mac) or scoop install sops (Windows)');
|
|
64
67
|
}
|
|
65
68
|
try {
|
|
66
69
|
execSync(`sops --input-type dotenv --output-type dotenv --encrypt "${inputPath}" > "${outputPath}"`, {
|
|
67
70
|
encoding: 'utf-8',
|
|
68
|
-
shell: '/bin/bash',
|
|
69
71
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
70
72
|
env: getSopsEnv(),
|
|
71
73
|
});
|
|
@@ -85,6 +87,7 @@ export function edit(filePath) {
|
|
|
85
87
|
const result = spawnSync('sops', ['--input-type', 'dotenv', '--output-type', 'dotenv', filePath], {
|
|
86
88
|
stdio: 'inherit',
|
|
87
89
|
env: getSopsEnv(),
|
|
90
|
+
shell: true // Required to find executable on Windows
|
|
88
91
|
});
|
|
89
92
|
if (result.status !== 0) {
|
|
90
93
|
throw new Error(`SOPS edit failed with exit code ${result.status}`);
|
|
@@ -99,13 +102,10 @@ export function setKey(filePath, key, value) {
|
|
|
99
102
|
throw new Error('SOPS is not installed. Install with: brew install sops');
|
|
100
103
|
}
|
|
101
104
|
let content = '';
|
|
102
|
-
// If file exists, decrypt it first
|
|
103
105
|
if (existsSync(filePath)) {
|
|
104
106
|
content = decrypt(filePath);
|
|
105
107
|
}
|
|
106
|
-
// Parse existing content into lines
|
|
107
108
|
const lines = content.split('\n').filter(line => line.trim() !== '');
|
|
108
|
-
// Find and update or add the key
|
|
109
109
|
let found = false;
|
|
110
110
|
const updatedLines = lines.map(line => {
|
|
111
111
|
const match = line.match(/^([^=]+)=/);
|
|
@@ -122,16 +122,13 @@ export function setKey(filePath, key, value) {
|
|
|
122
122
|
const tempFile = join(tmpdir(), `hush-temp-${Date.now()}.env`);
|
|
123
123
|
try {
|
|
124
124
|
writeFileSync(tempFile, newContent, 'utf-8');
|
|
125
|
-
// Encrypt temp file to the target
|
|
126
125
|
execSync(`sops --input-type dotenv --output-type dotenv --encrypt "${tempFile}" > "${filePath}"`, {
|
|
127
126
|
encoding: 'utf-8',
|
|
128
|
-
shell: '/bin/bash',
|
|
129
127
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
130
128
|
env: getSopsEnv(),
|
|
131
129
|
});
|
|
132
130
|
}
|
|
133
131
|
finally {
|
|
134
|
-
// Always clean up temp file
|
|
135
132
|
if (existsSync(tempFile)) {
|
|
136
133
|
unlinkSync(tempFile);
|
|
137
134
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { OutputFormat, Environment, Target, SourceFiles, HushConfig, EnvVar, EncryptOptions, EditOptions, PushOptions, StatusOptions, InitOptions, ListOptions, } from './types.js';
|
|
1
|
+
export type { OutputFormat, Environment, Target, SourceFiles, HushConfig, EnvVar, DecryptOptions, EncryptOptions, EditOptions, PushOptions, StatusOptions, InitOptions, ListOptions, } from './types.js';
|
|
2
2
|
export { DEFAULT_SOURCES, FORMAT_OUTPUT_FILES } from './types.js';
|
|
3
3
|
export { loadConfig, findConfigPath, validateConfig } from './config/loader.js';
|
|
4
4
|
export { parseEnvContent, parseEnvFile, varsToRecord, recordToVars } from './core/parse.js';
|
|
@@ -9,6 +9,7 @@ export { decrypt, encrypt, edit, isSopsInstalled, isAgeKeyConfigured } from './c
|
|
|
9
9
|
export { maskValue, maskVars, formatMaskedVar } from './core/mask.js';
|
|
10
10
|
export type { MaskedVar } from './core/mask.js';
|
|
11
11
|
export { formatVars, formatDotenv, formatWrangler, formatJson, formatShell } from './formats/index.js';
|
|
12
|
+
export { decryptCommand } from './commands/decrypt.js';
|
|
12
13
|
export { encryptCommand } from './commands/encrypt.js';
|
|
13
14
|
export { editCommand } from './commands/edit.js';
|
|
14
15
|
export { statusCommand } from './commands/status.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,EACX,UAAU,EACV,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChH,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvG,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,EACX,UAAU,EACV,MAAM,EACN,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChH,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvG,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { mergeVars } from './core/merge.js';
|
|
|
7
7
|
export { decrypt, encrypt, edit, isSopsInstalled, isAgeKeyConfigured } from './core/sops.js';
|
|
8
8
|
export { maskValue, maskVars, formatMaskedVar } from './core/mask.js';
|
|
9
9
|
export { formatVars, formatDotenv, formatWrangler, formatJson, formatShell } from './formats/index.js';
|
|
10
|
+
export { decryptCommand } from './commands/decrypt.js';
|
|
10
11
|
export { encryptCommand } from './commands/encrypt.js';
|
|
11
12
|
export { editCommand } from './commands/edit.js';
|
|
12
13
|
export { statusCommand } from './commands/status.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface EnvVar {
|
|
|
27
27
|
export interface EncryptOptions {
|
|
28
28
|
root: string;
|
|
29
29
|
}
|
|
30
|
+
export interface DecryptOptions {
|
|
31
|
+
root: string;
|
|
32
|
+
env: Environment;
|
|
33
|
+
force: boolean;
|
|
34
|
+
}
|
|
30
35
|
export interface EditOptions {
|
|
31
36
|
root: string;
|
|
32
37
|
file?: 'shared' | 'development' | 'production' | 'local';
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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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"}
|