@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/commands/decrypt.js
CHANGED
|
@@ -1,105 +1,71 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { filterVarsForTarget } from '../core/filter.js';
|
|
6
|
+
import { interpolateVars, getUnresolvedVars } from '../core/interpolate.js';
|
|
7
|
+
import { mergeVars } from '../core/merge.js';
|
|
8
|
+
import { parseEnvContent, parseEnvFile } from '../core/parse.js';
|
|
6
9
|
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (pkg.style === 'wrangler') {
|
|
12
|
-
// Wrangler always uses .dev.vars (even for "prod" we generate dev vars for local testing)
|
|
13
|
-
return [{ path: '.dev.vars', env }];
|
|
14
|
-
}
|
|
15
|
-
// Standard style outputs environment-specific files
|
|
16
|
-
if (env === 'dev') {
|
|
17
|
-
return [{ path: '.env.development', env: 'dev' }];
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
return [{ path: '.env.production', env: 'prod' }];
|
|
21
|
-
}
|
|
10
|
+
import { formatVars } from '../formats/index.js';
|
|
11
|
+
import { FORMAT_OUTPUT_FILES } from '../types.js';
|
|
12
|
+
function getEncryptedPath(sourcePath) {
|
|
13
|
+
return sourcePath + '.encrypted';
|
|
22
14
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Filter variables for a package based on naming conventions
|
|
25
|
-
* - EXPO_PUBLIC_* vars only go to non-wrangler packages
|
|
26
|
-
* - Other vars go to wrangler packages
|
|
27
|
-
*/
|
|
28
|
-
function filterVarsForPackage(vars, pkg) {
|
|
29
|
-
if (pkg.style === 'wrangler') {
|
|
30
|
-
// Wrangler gets everything EXCEPT EXPO_PUBLIC_* vars
|
|
31
|
-
return vars.filter((v) => !v.key.startsWith('EXPO_PUBLIC_'));
|
|
32
|
-
}
|
|
33
|
-
// Standard packages get all vars (including EXPO_PUBLIC_*)
|
|
34
|
-
return vars;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Decrypt command - decrypt .env.encrypted and generate env files for all packages
|
|
38
|
-
*/
|
|
39
15
|
export async function decryptCommand(options) {
|
|
40
|
-
const { root, env
|
|
41
|
-
const
|
|
16
|
+
const { root, env } = options;
|
|
17
|
+
const config = loadConfig(root);
|
|
18
|
+
console.log(pc.blue(`Decrypting for ${env}...`));
|
|
19
|
+
const sharedEncrypted = join(root, getEncryptedPath(config.sources.shared));
|
|
20
|
+
const envEncrypted = join(root, getEncryptedPath(config.sources[env]));
|
|
42
21
|
const localPath = join(root, '.env.local');
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
22
|
+
const varSources = [];
|
|
23
|
+
if (existsSync(sharedEncrypted)) {
|
|
24
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
25
|
+
const vars = parseEnvContent(content);
|
|
26
|
+
varSources.push(vars);
|
|
27
|
+
console.log(pc.dim(` ${config.sources.shared}.encrypted: ${vars.length} vars`));
|
|
28
|
+
}
|
|
29
|
+
if (existsSync(envEncrypted)) {
|
|
30
|
+
const content = sopsDecrypt(envEncrypted);
|
|
31
|
+
const vars = parseEnvContent(content);
|
|
32
|
+
varSources.push(vars);
|
|
33
|
+
console.log(pc.dim(` ${config.sources[env]}.encrypted: ${vars.length} vars`));
|
|
48
34
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
decryptedContent = sopsDecrypt(encryptedPath);
|
|
35
|
+
if (existsSync(localPath)) {
|
|
36
|
+
const vars = parseEnvFile(localPath);
|
|
37
|
+
varSources.push(vars);
|
|
38
|
+
console.log(pc.dim(` .env.local: ${vars.length} vars (overrides)`));
|
|
54
39
|
}
|
|
55
|
-
|
|
56
|
-
console.error(pc.red(
|
|
40
|
+
if (varSources.length === 0) {
|
|
41
|
+
console.error(pc.red('No encrypted files found'));
|
|
42
|
+
console.error(pc.dim(`Expected: ${sharedEncrypted}`));
|
|
57
43
|
process.exit(1);
|
|
58
44
|
}
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log(pc.dim(` ${envVars.length} variables for ${env} environment`));
|
|
65
|
-
// Load local overrides if they exist
|
|
66
|
-
const localVars = parseEnvFile(localPath);
|
|
67
|
-
if (localVars.length > 0) {
|
|
68
|
-
console.log(pc.dim(` ${localVars.length} local overrides from .env.local`));
|
|
45
|
+
const merged = mergeVars(...varSources);
|
|
46
|
+
const interpolated = interpolateVars(merged);
|
|
47
|
+
const unresolved = getUnresolvedVars(interpolated);
|
|
48
|
+
if (unresolved.length > 0) {
|
|
49
|
+
console.warn(pc.yellow(` Warning: ${unresolved.length} vars have unresolved references`));
|
|
69
50
|
}
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Generate output files for each package
|
|
77
|
-
for (const pkg of packages) {
|
|
78
|
-
const pkgDir = pkg.path ? join(root, pkg.path) : root;
|
|
79
|
-
const outputFiles = getOutputFiles(pkg, env);
|
|
80
|
-
const pkgVars = filterVarsForPackage(expandedVars, pkg);
|
|
81
|
-
if (pkgVars.length === 0) {
|
|
82
|
-
console.log(pc.dim(` ${pkg.path || '.'} (${pkg.style}) - no applicable vars, skipped`));
|
|
51
|
+
console.log(pc.blue(`\nWriting to ${config.targets.length} targets:`));
|
|
52
|
+
for (const target of config.targets) {
|
|
53
|
+
const targetDir = join(root, target.path);
|
|
54
|
+
const filtered = filterVarsForTarget(interpolated, target);
|
|
55
|
+
if (filtered.length === 0) {
|
|
56
|
+
console.log(pc.dim(` ${target.path}/ - no matching vars, skipped`));
|
|
83
57
|
continue;
|
|
84
58
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!existsSync(dir)) {
|
|
90
|
-
mkdirSync(dir, { recursive: true });
|
|
91
|
-
}
|
|
92
|
-
// Write the file
|
|
93
|
-
const content = formatEnvFile(pkgVars);
|
|
94
|
-
writeFileSync(outputPath, content, 'utf-8');
|
|
95
|
-
const relativePath = pkg.path ? `${pkg.path}/${output.path}` : output.path;
|
|
96
|
-
console.log(pc.green(` ${relativePath}`) +
|
|
97
|
-
pc.dim(` (${pkg.style}, ${pkgVars.length} vars)`));
|
|
59
|
+
const outputFilename = FORMAT_OUTPUT_FILES[target.format][env];
|
|
60
|
+
const outputPath = join(targetDir, outputFilename);
|
|
61
|
+
if (!existsSync(targetDir)) {
|
|
62
|
+
mkdirSync(targetDir, { recursive: true });
|
|
98
63
|
}
|
|
64
|
+
const content = formatVars(filtered, target.format);
|
|
65
|
+
writeFileSync(outputPath, content, 'utf-8');
|
|
66
|
+
const relativePath = target.path === '.' ? outputFilename : `${target.path}/${outputFilename}`;
|
|
67
|
+
console.log(pc.green(` ${relativePath}`) +
|
|
68
|
+
pc.dim(` (${target.format}, ${filtered.length} vars)`));
|
|
99
69
|
}
|
|
100
|
-
// Also write root .env with shared vars (useful for scripts)
|
|
101
|
-
const rootEnvPath = join(root, '.env');
|
|
102
|
-
writeFileSync(rootEnvPath, formatEnvFile(expandedVars), 'utf-8');
|
|
103
|
-
console.log(pc.green(` .env`) + pc.dim(` (root, ${expandedVars.length} vars)`));
|
|
104
70
|
console.log(pc.green('\nDecryption complete'));
|
|
105
71
|
}
|
package/dist/commands/edit.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
root: string;
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Edit command - open .env.encrypted in editor via SOPS
|
|
6
|
-
*/
|
|
1
|
+
import type { EditOptions } from '../types.js';
|
|
7
2
|
export declare function editCommand(options: EditOptions): Promise<void>;
|
|
8
|
-
export {};
|
|
9
3
|
//# sourceMappingURL=edit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/commands/edit.ts"],"names":[],"mappings":"AAKA,
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/commands/edit.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE"}
|
package/dist/commands/edit.js
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
4
5
|
import { edit as sopsEdit } from '../core/sops.js';
|
|
5
|
-
/**
|
|
6
|
-
* Edit command - open .env.encrypted in editor via SOPS
|
|
7
|
-
*/
|
|
8
6
|
export async function editCommand(options) {
|
|
9
|
-
const { root } = options;
|
|
10
|
-
const
|
|
11
|
-
|
|
7
|
+
const { root, file } = options;
|
|
8
|
+
const config = loadConfig(root);
|
|
9
|
+
const fileKey = file ?? 'shared';
|
|
10
|
+
const sourcePath = config.sources[fileKey];
|
|
11
|
+
const encryptedPath = join(root, sourcePath + '.encrypted');
|
|
12
12
|
if (!existsSync(encryptedPath)) {
|
|
13
|
-
console.error(pc.red(`
|
|
14
|
-
console.error(pc.dim('
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
console.log(pc.blue('Opening encrypted file in editor...'));
|
|
18
|
-
console.log(pc.dim(' (Changes will be encrypted on save)'));
|
|
19
|
-
try {
|
|
20
|
-
sopsEdit(encryptedPath);
|
|
21
|
-
console.log(pc.green('\nEdit complete'));
|
|
22
|
-
console.log(pc.dim(' Run "pnpm secrets decrypt" to update local env files.'));
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error(pc.red(error.message));
|
|
13
|
+
console.error(pc.red(`Encrypted file not found: ${sourcePath}.encrypted`));
|
|
14
|
+
console.error(pc.dim('Run "hush encrypt" first to create encrypted files'));
|
|
26
15
|
process.exit(1);
|
|
27
16
|
}
|
|
17
|
+
console.log(pc.blue(`Editing ${sourcePath}.encrypted...`));
|
|
18
|
+
console.log(pc.dim('Changes will be encrypted on save'));
|
|
19
|
+
sopsEdit(encryptedPath);
|
|
20
|
+
console.log(pc.green('\nEdit complete'));
|
|
21
|
+
console.log(pc.dim('Run "hush decrypt" to regenerate local env files'));
|
|
28
22
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
root: string;
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Encrypt command - encrypt .env to .env.encrypted
|
|
6
|
-
*/
|
|
1
|
+
import type { EncryptOptions } from '../types.js';
|
|
7
2
|
export declare function encryptCommand(options: EncryptOptions): Promise<void>;
|
|
8
|
-
export {};
|
|
9
3
|
//# sourceMappingURL=encrypt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/commands/encrypt.ts"],"names":[],"mappings":"AAKA,
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/commands/encrypt.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC3E"}
|
package/dist/commands/encrypt.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
4
5
|
import { encrypt as sopsEncrypt } from '../core/sops.js';
|
|
5
|
-
/**
|
|
6
|
-
* Encrypt command - encrypt .env to .env.encrypted
|
|
7
|
-
*/
|
|
8
6
|
export async function encryptCommand(options) {
|
|
9
7
|
const { root } = options;
|
|
10
|
-
const
|
|
11
|
-
const outputPath = join(root, '.env.encrypted');
|
|
12
|
-
// Check input file exists
|
|
13
|
-
if (!existsSync(inputPath)) {
|
|
14
|
-
console.error(pc.red(`Error: ${inputPath} not found`));
|
|
15
|
-
console.error(pc.dim('Create a .env file first, then run encrypt.'));
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
8
|
+
const config = loadConfig(root);
|
|
18
9
|
console.log(pc.blue('Encrypting secrets...'));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
const sourceFiles = [
|
|
11
|
+
{ key: 'shared', path: config.sources.shared },
|
|
12
|
+
{ key: 'development', path: config.sources.development },
|
|
13
|
+
{ key: 'production', path: config.sources.production },
|
|
14
|
+
];
|
|
15
|
+
let encryptedCount = 0;
|
|
16
|
+
for (const { key, path } of sourceFiles) {
|
|
17
|
+
const sourcePath = join(root, path);
|
|
18
|
+
const encryptedPath = sourcePath + '.encrypted';
|
|
19
|
+
if (!existsSync(sourcePath)) {
|
|
20
|
+
console.log(pc.dim(` ${path} - not found, skipping`));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
sopsEncrypt(sourcePath, encryptedPath);
|
|
24
|
+
encryptedCount++;
|
|
25
|
+
console.log(pc.green(` ${path}`) + pc.dim(` -> ${path}.encrypted`));
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
console.error(pc.red(
|
|
27
|
+
if (encryptedCount === 0) {
|
|
28
|
+
console.error(pc.red('\nNo source files found to encrypt'));
|
|
29
|
+
console.error(pc.dim('Create at least .env with your secrets'));
|
|
28
30
|
process.exit(1);
|
|
29
31
|
}
|
|
32
|
+
console.log(pc.green(`\nEncrypted ${encryptedCount} file(s)`));
|
|
33
|
+
console.log(pc.dim('You can now commit the .encrypted files to git'));
|
|
30
34
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has.d.ts","sourceRoot":"","sources":["../../src/commands/has.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CnE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { interpolateVars } from '../core/interpolate.js';
|
|
6
|
+
import { mergeVars } from '../core/merge.js';
|
|
7
|
+
import { parseEnvContent } from '../core/parse.js';
|
|
8
|
+
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
9
|
+
export async function hasCommand(options) {
|
|
10
|
+
const { root, env, key, quiet } = options;
|
|
11
|
+
const config = loadConfig(root);
|
|
12
|
+
const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
|
|
13
|
+
const envEncrypted = join(root, config.sources[env] + '.encrypted');
|
|
14
|
+
const varSources = [];
|
|
15
|
+
if (existsSync(sharedEncrypted)) {
|
|
16
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
17
|
+
varSources.push(parseEnvContent(content));
|
|
18
|
+
}
|
|
19
|
+
if (existsSync(envEncrypted)) {
|
|
20
|
+
const content = sopsDecrypt(envEncrypted);
|
|
21
|
+
varSources.push(parseEnvContent(content));
|
|
22
|
+
}
|
|
23
|
+
if (varSources.length === 0) {
|
|
24
|
+
if (!quiet) {
|
|
25
|
+
console.error(pc.red('No encrypted files found'));
|
|
26
|
+
}
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
const merged = mergeVars(...varSources);
|
|
30
|
+
const interpolated = interpolateVars(merged);
|
|
31
|
+
const found = interpolated.find(v => v.key === key);
|
|
32
|
+
const exists = found !== undefined && found.value.length > 0;
|
|
33
|
+
if (!quiet) {
|
|
34
|
+
if (exists) {
|
|
35
|
+
console.log(pc.green(`${key} is set (${found.value.length} chars)`));
|
|
36
|
+
}
|
|
37
|
+
else if (found) {
|
|
38
|
+
console.log(pc.yellow(`${key} exists but is empty`));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(pc.red(`${key} not found`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
process.exit(exists ? 0 : 1);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAc,WAAW,EAAU,MAAM,aAAa,CAAC;AAqCnE,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCrE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import { findConfigPath } from '../config/loader.js';
|
|
6
|
+
import { DEFAULT_SOURCES } from '../types.js';
|
|
7
|
+
function detectTargets(root) {
|
|
8
|
+
const targets = [{ name: 'root', path: '.', format: 'dotenv' }];
|
|
9
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
if (!entry.isDirectory())
|
|
12
|
+
continue;
|
|
13
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
14
|
+
continue;
|
|
15
|
+
const dirPath = join(root, entry.name);
|
|
16
|
+
const packageJsonPath = join(dirPath, 'package.json');
|
|
17
|
+
const wranglerPath = join(dirPath, 'wrangler.toml');
|
|
18
|
+
if (!existsSync(packageJsonPath))
|
|
19
|
+
continue;
|
|
20
|
+
if (existsSync(wranglerPath)) {
|
|
21
|
+
targets.push({
|
|
22
|
+
name: entry.name,
|
|
23
|
+
path: `./${entry.name}`,
|
|
24
|
+
format: 'wrangler',
|
|
25
|
+
exclude: ['EXPO_PUBLIC_*', 'NEXT_PUBLIC_*', 'VITE_*'],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
targets.push({
|
|
30
|
+
name: entry.name,
|
|
31
|
+
path: `./${entry.name}`,
|
|
32
|
+
format: 'dotenv',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return targets;
|
|
37
|
+
}
|
|
38
|
+
export async function initCommand(options) {
|
|
39
|
+
const { root } = options;
|
|
40
|
+
const existingConfig = findConfigPath(root);
|
|
41
|
+
if (existingConfig) {
|
|
42
|
+
console.error(pc.red(`Config already exists: ${existingConfig}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log(pc.blue('Initializing hush...'));
|
|
46
|
+
const targets = detectTargets(root);
|
|
47
|
+
const config = {
|
|
48
|
+
sources: DEFAULT_SOURCES,
|
|
49
|
+
targets,
|
|
50
|
+
};
|
|
51
|
+
const yaml = stringifyYaml(config, { indent: 2 });
|
|
52
|
+
const configPath = join(root, 'hush.yaml');
|
|
53
|
+
writeFileSync(configPath, yaml, 'utf-8');
|
|
54
|
+
console.log(pc.green(`\nCreated ${configPath}`));
|
|
55
|
+
console.log(pc.dim('\nDetected targets:'));
|
|
56
|
+
for (const target of targets) {
|
|
57
|
+
console.log(` ${pc.cyan(target.name)} ${pc.dim(target.path)} ${pc.magenta(target.format)}`);
|
|
58
|
+
}
|
|
59
|
+
console.log(pc.dim('\nNext steps:'));
|
|
60
|
+
console.log(' 1. Create your .env files (.env, .env.development, .env.production)');
|
|
61
|
+
console.log(' 2. Run "hush encrypt" to encrypt them');
|
|
62
|
+
console.log(' 3. Run "hush decrypt" to generate local env files');
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD3E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { filterVarsForTarget, describeFilter } from '../core/filter.js';
|
|
6
|
+
import { interpolateVars } from '../core/interpolate.js';
|
|
7
|
+
import { maskVars, formatMaskedVar } from '../core/mask.js';
|
|
8
|
+
import { mergeVars } from '../core/merge.js';
|
|
9
|
+
import { parseEnvContent } from '../core/parse.js';
|
|
10
|
+
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
11
|
+
export async function inspectCommand(options) {
|
|
12
|
+
const { root, env } = options;
|
|
13
|
+
const config = loadConfig(root);
|
|
14
|
+
const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
|
|
15
|
+
const envEncrypted = join(root, config.sources[env] + '.encrypted');
|
|
16
|
+
const varSources = [];
|
|
17
|
+
if (existsSync(sharedEncrypted)) {
|
|
18
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
19
|
+
varSources.push(parseEnvContent(content));
|
|
20
|
+
}
|
|
21
|
+
if (existsSync(envEncrypted)) {
|
|
22
|
+
const content = sopsDecrypt(envEncrypted);
|
|
23
|
+
varSources.push(parseEnvContent(content));
|
|
24
|
+
}
|
|
25
|
+
if (varSources.length === 0) {
|
|
26
|
+
console.error(pc.red('No encrypted files found'));
|
|
27
|
+
console.error(pc.dim(`Expected: ${sharedEncrypted}`));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const merged = mergeVars(...varSources);
|
|
31
|
+
const interpolated = interpolateVars(merged);
|
|
32
|
+
const masked = maskVars(interpolated);
|
|
33
|
+
const maxKeyLen = Math.max(...masked.map(v => v.key.length));
|
|
34
|
+
console.log(pc.blue(`\nSecrets for ${env}:\n`));
|
|
35
|
+
for (const v of masked) {
|
|
36
|
+
const line = formatMaskedVar(v, maxKeyLen);
|
|
37
|
+
console.log(` ${v.isSet ? pc.green(v.key.padEnd(maxKeyLen)) : pc.yellow(v.key.padEnd(maxKeyLen))} = ${v.isSet ? pc.dim(v.masked + ` (${v.length} chars)`) : pc.yellow('(not set)')}`);
|
|
38
|
+
}
|
|
39
|
+
console.log(pc.dim(`\nTotal: ${masked.length} variables\n`));
|
|
40
|
+
console.log(pc.blue('Target distribution:\n'));
|
|
41
|
+
for (const target of config.targets) {
|
|
42
|
+
const filtered = filterVarsForTarget(interpolated, target);
|
|
43
|
+
const filter = describeFilter(target);
|
|
44
|
+
console.log(` ${pc.cyan(target.name)} ${pc.dim(`(${target.path}/)`)} - ${filtered.length} vars`);
|
|
45
|
+
if (filter !== 'all vars') {
|
|
46
|
+
console.log(` ${pc.dim(filter)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { interpolateVars } from '../core/interpolate.js';
|
|
6
|
+
import { mergeVars } from '../core/merge.js';
|
|
7
|
+
import { parseEnvContent } from '../core/parse.js';
|
|
8
|
+
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
9
|
+
export async function listCommand(options) {
|
|
10
|
+
const { root, env } = options;
|
|
11
|
+
const config = loadConfig(root);
|
|
12
|
+
console.log(pc.blue(`Variables for ${env}:\n`));
|
|
13
|
+
const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
|
|
14
|
+
const envEncrypted = join(root, config.sources[env] + '.encrypted');
|
|
15
|
+
const varSources = [];
|
|
16
|
+
if (existsSync(sharedEncrypted)) {
|
|
17
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
18
|
+
varSources.push(parseEnvContent(content));
|
|
19
|
+
}
|
|
20
|
+
if (existsSync(envEncrypted)) {
|
|
21
|
+
const content = sopsDecrypt(envEncrypted);
|
|
22
|
+
varSources.push(parseEnvContent(content));
|
|
23
|
+
}
|
|
24
|
+
if (varSources.length === 0) {
|
|
25
|
+
console.error(pc.red('No encrypted files found'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const merged = mergeVars(...varSources);
|
|
29
|
+
const interpolated = interpolateVars(merged);
|
|
30
|
+
for (const { key, value } of interpolated) {
|
|
31
|
+
const displayValue = value.length > 50 ? value.slice(0, 47) + '...' : value;
|
|
32
|
+
console.log(`${pc.cyan(key)}=${pc.dim(displayValue)}`);
|
|
33
|
+
}
|
|
34
|
+
console.log(pc.dim(`\nTotal: ${interpolated.length} variables`));
|
|
35
|
+
}
|
package/dist/commands/push.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
root: string;
|
|
3
|
-
dryRun?: boolean;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Push command - push production secrets to Cloudflare Workers
|
|
7
|
-
*/
|
|
1
|
+
import type { PushOptions } from '../types.js';
|
|
8
2
|
export declare function pushCommand(options: PushOptions): Promise<void>;
|
|
9
|
-
export {};
|
|
10
3
|
//# sourceMappingURL=push.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,WAAW,EAAE,MAAM,aAAa,CAAC;AAsBvD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiErE"}
|