@chriscode/hush 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/cli.js +189 -74
  2. package/dist/commands/check.d.ts +4 -0
  3. package/dist/commands/check.d.ts.map +1 -0
  4. package/dist/commands/check.js +249 -0
  5. package/dist/commands/decrypt.d.ts +1 -9
  6. package/dist/commands/decrypt.d.ts.map +1 -1
  7. package/dist/commands/decrypt.js +54 -88
  8. package/dist/commands/edit.d.ts +1 -7
  9. package/dist/commands/edit.d.ts.map +1 -1
  10. package/dist/commands/edit.js +13 -19
  11. package/dist/commands/encrypt.d.ts +1 -7
  12. package/dist/commands/encrypt.d.ts.map +1 -1
  13. package/dist/commands/encrypt.js +23 -19
  14. package/dist/commands/has.d.ts +9 -0
  15. package/dist/commands/has.d.ts.map +1 -0
  16. package/dist/commands/has.js +45 -0
  17. package/dist/commands/init.d.ts +3 -0
  18. package/dist/commands/init.d.ts.map +1 -0
  19. package/dist/commands/init.js +63 -0
  20. package/dist/commands/inspect.d.ts +7 -0
  21. package/dist/commands/inspect.d.ts.map +1 -0
  22. package/dist/commands/inspect.js +50 -0
  23. package/dist/commands/list.d.ts +3 -0
  24. package/dist/commands/list.d.ts.map +1 -0
  25. package/dist/commands/list.js +35 -0
  26. package/dist/commands/push.d.ts +1 -8
  27. package/dist/commands/push.d.ts.map +1 -1
  28. package/dist/commands/push.js +45 -63
  29. package/dist/commands/status.d.ts +1 -7
  30. package/dist/commands/status.d.ts.map +1 -1
  31. package/dist/commands/status.js +43 -45
  32. package/dist/config/loader.d.ts +5 -0
  33. package/dist/config/loader.d.ts.map +1 -0
  34. package/dist/config/loader.js +50 -0
  35. package/dist/core/filter.d.ts +4 -0
  36. package/dist/core/filter.d.ts.map +1 -0
  37. package/dist/core/filter.js +27 -0
  38. package/dist/core/interpolate.d.ts +6 -0
  39. package/dist/core/interpolate.d.ts.map +1 -0
  40. package/dist/core/interpolate.js +43 -0
  41. package/dist/core/mask.d.ts +11 -0
  42. package/dist/core/mask.d.ts.map +1 -0
  43. package/dist/core/mask.js +30 -0
  44. package/dist/core/merge.d.ts +3 -0
  45. package/dist/core/merge.d.ts.map +1 -0
  46. package/dist/core/merge.js +9 -0
  47. package/dist/core/parse.d.ts +3 -27
  48. package/dist/core/parse.d.ts.map +1 -1
  49. package/dist/core/parse.js +8 -77
  50. package/dist/core/sops.d.ts +1 -12
  51. package/dist/core/sops.d.ts.map +1 -1
  52. package/dist/core/sops.js +4 -25
  53. package/dist/formats/dotenv.d.ts +3 -0
  54. package/dist/formats/dotenv.d.ts.map +1 -0
  55. package/dist/formats/dotenv.js +3 -0
  56. package/dist/formats/index.d.ts +8 -0
  57. package/dist/formats/index.d.ts.map +1 -0
  58. package/dist/formats/index.js +17 -0
  59. package/dist/formats/json.d.ts +3 -0
  60. package/dist/formats/json.d.ts.map +1 -0
  61. package/dist/formats/json.js +7 -0
  62. package/dist/formats/shell.d.ts +3 -0
  63. package/dist/formats/shell.d.ts.map +1 -0
  64. package/dist/formats/shell.js +11 -0
  65. package/dist/formats/wrangler.d.ts +3 -0
  66. package/dist/formats/wrangler.d.ts.map +1 -0
  67. package/dist/formats/wrangler.js +3 -0
  68. package/dist/index.d.ts +17 -7
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +15 -9
  71. package/dist/lib/diff.d.ts +12 -0
  72. package/dist/lib/diff.d.ts.map +1 -0
  73. package/dist/lib/diff.js +19 -0
  74. package/dist/types.d.ts +65 -31
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +22 -6
  77. package/package.json +15 -14
  78. package/README.md +0 -194
  79. package/dist/core/discover.d.ts +0 -6
  80. package/dist/core/discover.d.ts.map +0 -1
  81. package/dist/core/discover.js +0 -81
@@ -1,105 +1,71 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
2
+ import { join } from 'node:path';
3
3
  import pc from 'picocolors';
4
- import { discoverPackages } from '../core/discover.js';
5
- import { expandVariables, formatEnvFile, getVarsForEnvironment, mergeEnvVars, parseEnvContent, parseEnvFile, } from '../core/parse.js';
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
- * Get output files for a package based on its style and environment
9
- */
10
- function getOutputFiles(pkg, env) {
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 = 'dev' } = options;
41
- const encryptedPath = join(root, '.env.encrypted');
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
- // Check encrypted file exists
44
- if (!existsSync(encryptedPath)) {
45
- console.error(pc.red(`Error: ${encryptedPath} not found`));
46
- console.error(pc.dim('Create it with: pnpm secrets encrypt (after creating .env)'));
47
- process.exit(1);
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
- console.log(pc.blue('Decrypting secrets...'));
50
- // Decrypt the encrypted file
51
- let decryptedContent;
52
- try {
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
- catch (error) {
56
- console.error(pc.red(error.message));
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
- // Parse decrypted content
60
- const allVars = parseEnvContent(decryptedContent);
61
- console.log(pc.dim(` Parsed ${allVars.length} variables from .env.encrypted`));
62
- // Get vars for the target environment
63
- const envVars = getVarsForEnvironment(allVars, env);
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
- // Merge and expand
71
- const mergedVars = mergeEnvVars(envVars, localVars);
72
- const expandedVars = expandVariables(mergedVars);
73
- // Discover packages
74
- const packages = await discoverPackages(root);
75
- console.log(pc.blue(`\nDiscovered ${packages.length} packages:`));
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
- for (const output of outputFiles) {
86
- const outputPath = join(pkgDir, output.path);
87
- // Ensure directory exists
88
- const dir = dirname(outputPath);
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
  }
@@ -1,9 +1,3 @@
1
- interface EditOptions {
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,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBrE"}
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"}
@@ -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 encryptedPath = join(root, '.env.encrypted');
11
- // Check encrypted file exists
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(`Error: ${encryptedPath} not found`));
14
- console.error(pc.dim('Create it with: pnpm secrets encrypt (after creating .env)'));
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
- interface EncryptOptions {
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,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
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"}
@@ -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 inputPath = join(root, '.env');
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
- console.log(pc.dim(` Input: .env`));
20
- console.log(pc.dim(` Output: .env.encrypted`));
21
- try {
22
- sopsEncrypt(inputPath, outputPath);
23
- console.log(pc.green('\nEncryption complete'));
24
- console.log(pc.dim(' You can now commit .env.encrypted to git.'));
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
- catch (error) {
27
- console.error(pc.red(error.message));
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,9 @@
1
+ import type { Environment } from '../types.js';
2
+ export interface HasOptions {
3
+ root: string;
4
+ env: Environment;
5
+ key: string;
6
+ quiet: boolean;
7
+ }
8
+ export declare function hasCommand(options: HasOptions): Promise<void>;
9
+ //# sourceMappingURL=has.d.ts.map
@@ -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,3 @@
1
+ import type { InitOptions } from '../types.js';
2
+ export declare function initCommand(options: InitOptions): Promise<void>;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -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,7 @@
1
+ import type { Environment } from '../types.js';
2
+ export interface InspectOptions {
3
+ root: string;
4
+ env: Environment;
5
+ }
6
+ export declare function inspectCommand(options: InspectOptions): Promise<void>;
7
+ //# sourceMappingURL=inspect.d.ts.map
@@ -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,3 @@
1
+ import type { ListOptions } from '../types.js';
2
+ export declare function listCommand(options: ListOptions): Promise<void>;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -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
+ }
@@ -1,10 +1,3 @@
1
- interface PushOptions {
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":"AAaA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0CD;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqErE"}
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"}