@chriscode/hush 2.0.0 → 2.2.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.
@@ -3,4 +3,9 @@ export declare function isAgeKeyConfigured(): boolean;
3
3
  export declare function decrypt(filePath: string): string;
4
4
  export declare function encrypt(inputPath: string, outputPath: string): void;
5
5
  export declare function edit(filePath: string): void;
6
+ /**
7
+ * Set a single key in an encrypted file.
8
+ * Decrypts to memory, updates the key, re-encrypts.
9
+ */
10
+ export declare function setKey(filePath: string, key: string, value: string): void;
6
11
  //# sourceMappingURL=sops.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"AAyBA,wBAAgB,eAAe,IAAI,OAAO,CAOzC;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,CAuBnE;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAqB3C"}
1
+ {"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"AA0BA,wBAAgB,eAAe,IAAI,OAAO,CAOzC;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,CAuBnE;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAqB3C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAqDzE"}
package/dist/core/sops.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { execSync, spawnSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, writeFileSync, unlinkSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
4
5
  function getAgeKeyFile() {
5
6
  if (process.env.SOPS_AGE_KEY_FILE) {
6
7
  return process.env.SOPS_AGE_KEY_FILE;
@@ -89,3 +90,50 @@ export function edit(filePath) {
89
90
  throw new Error(`SOPS edit failed with exit code ${result.status}`);
90
91
  }
91
92
  }
93
+ /**
94
+ * Set a single key in an encrypted file.
95
+ * Decrypts to memory, updates the key, re-encrypts.
96
+ */
97
+ export function setKey(filePath, key, value) {
98
+ if (!isSopsInstalled()) {
99
+ throw new Error('SOPS is not installed. Install with: brew install sops');
100
+ }
101
+ let content = '';
102
+ // If file exists, decrypt it first
103
+ if (existsSync(filePath)) {
104
+ content = decrypt(filePath);
105
+ }
106
+ // Parse existing content into lines
107
+ const lines = content.split('\n').filter(line => line.trim() !== '');
108
+ // Find and update or add the key
109
+ let found = false;
110
+ const updatedLines = lines.map(line => {
111
+ const match = line.match(/^([^=]+)=/);
112
+ if (match && match[1] === key) {
113
+ found = true;
114
+ return `${key}=${value}`;
115
+ }
116
+ return line;
117
+ });
118
+ if (!found) {
119
+ updatedLines.push(`${key}=${value}`);
120
+ }
121
+ const newContent = updatedLines.join('\n') + '\n';
122
+ const tempFile = join(tmpdir(), `hush-temp-${Date.now()}.env`);
123
+ try {
124
+ writeFileSync(tempFile, newContent, 'utf-8');
125
+ // Encrypt temp file to the target
126
+ execSync(`sops --input-type dotenv --output-type dotenv --encrypt "${tempFile}" > "${filePath}"`, {
127
+ encoding: 'utf-8',
128
+ shell: '/bin/bash',
129
+ stdio: ['pipe', 'pipe', 'pipe'],
130
+ env: getSopsEnv(),
131
+ });
132
+ }
133
+ finally {
134
+ // Always clean up temp file
135
+ if (existsSync(tempFile)) {
136
+ unlinkSync(tempFile);
137
+ }
138
+ }
139
+ }
@@ -3,6 +3,7 @@ import { formatDotenv } from './dotenv.js';
3
3
  import { formatJson } from './json.js';
4
4
  import { formatShell } from './shell.js';
5
5
  import { formatWrangler } from './wrangler.js';
6
+ import { formatYaml } from './yaml.js';
6
7
  export declare function formatVars(vars: EnvVar[], format: OutputFormat): string;
7
- export { formatDotenv, formatJson, formatShell, formatWrangler };
8
+ export { formatDotenv, formatJson, formatShell, formatWrangler, formatYaml };
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formats/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAWvE;AAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formats/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAavE;AAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC"}
@@ -2,6 +2,7 @@ import { formatDotenv } from './dotenv.js';
2
2
  import { formatJson } from './json.js';
3
3
  import { formatShell } from './shell.js';
4
4
  import { formatWrangler } from './wrangler.js';
5
+ import { formatYaml } from './yaml.js';
5
6
  export function formatVars(vars, format) {
6
7
  switch (format) {
7
8
  case 'dotenv':
@@ -12,6 +13,8 @@ export function formatVars(vars, format) {
12
13
  return formatJson(vars);
13
14
  case 'shell':
14
15
  return formatShell(vars);
16
+ case 'yaml':
17
+ return formatYaml(vars);
15
18
  }
16
19
  }
17
- export { formatDotenv, formatJson, formatShell, formatWrangler };
20
+ export { formatDotenv, formatJson, formatShell, formatWrangler, formatYaml };
@@ -0,0 +1,13 @@
1
+ import type { EnvVar } from '../types.js';
2
+ /**
3
+ * Format environment variables as YAML.
4
+ * Useful for Kubernetes ConfigMaps, Docker Compose, and other YAML-based configs.
5
+ *
6
+ * Output format:
7
+ * ```yaml
8
+ * DATABASE_URL: "postgres://localhost/db"
9
+ * API_KEY: "sk_test_xxx"
10
+ * ```
11
+ */
12
+ export declare function formatYaml(vars: EnvVar[]): string;
13
+ //# sourceMappingURL=yaml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../../src/formats/yaml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CA6CjD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Format environment variables as YAML.
3
+ * Useful for Kubernetes ConfigMaps, Docker Compose, and other YAML-based configs.
4
+ *
5
+ * Output format:
6
+ * ```yaml
7
+ * DATABASE_URL: "postgres://localhost/db"
8
+ * API_KEY: "sk_test_xxx"
9
+ * ```
10
+ */
11
+ export function formatYaml(vars) {
12
+ if (vars.length === 0) {
13
+ return '{}\n';
14
+ }
15
+ return (vars
16
+ .map(({ key, value }) => {
17
+ // YAML string escaping rules:
18
+ // - Simple alphanumeric values don't need quotes
19
+ // - Values with special chars need double quotes
20
+ // - Double quotes inside values need escaping
21
+ const needsQuotes = value === '' ||
22
+ value.includes(':') ||
23
+ value.includes('#') ||
24
+ value.includes("'") ||
25
+ value.includes('"') ||
26
+ value.includes('\n') ||
27
+ value.includes('\\') ||
28
+ value.startsWith(' ') ||
29
+ value.endsWith(' ') ||
30
+ value.startsWith('!') ||
31
+ value.startsWith('&') ||
32
+ value.startsWith('*') ||
33
+ value.startsWith('|') ||
34
+ value.startsWith('>') ||
35
+ value.startsWith('%') ||
36
+ value.startsWith('@') ||
37
+ value.startsWith('`') ||
38
+ /^(true|false|yes|no|on|off|null|~)$/i.test(value) ||
39
+ /^-?\d+(\.\d+)?$/.test(value) ||
40
+ /^0x[0-9a-fA-F]+$/.test(value) ||
41
+ /^0o[0-7]+$/.test(value);
42
+ if (needsQuotes) {
43
+ // Escape backslashes and double quotes for YAML double-quoted strings
44
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
45
+ return `${key}: "${escaped}"`;
46
+ }
47
+ return `${key}: ${value}`;
48
+ })
49
+ .join('\n') + '\n');
50
+ }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type OutputFormat = 'dotenv' | 'wrangler' | 'json' | 'shell';
1
+ export type OutputFormat = 'dotenv' | 'wrangler' | 'json' | 'shell' | 'yaml';
2
2
  export type Environment = 'development' | 'production';
3
3
  export interface Target {
4
4
  name: string;
@@ -11,6 +11,7 @@ export interface SourceFiles {
11
11
  shared: string;
12
12
  development: string;
13
13
  production: string;
14
+ local: string;
14
15
  }
15
16
  export interface HushConfig {
16
17
  sources: SourceFiles;
@@ -29,7 +30,18 @@ export interface EncryptOptions {
29
30
  }
30
31
  export interface EditOptions {
31
32
  root: string;
32
- file?: 'shared' | 'development' | 'production';
33
+ file?: 'shared' | 'development' | 'production' | 'local';
34
+ }
35
+ export interface SetOptions {
36
+ root: string;
37
+ file?: 'shared' | 'development' | 'production' | 'local';
38
+ key?: string;
39
+ }
40
+ export interface RunOptions {
41
+ root: string;
42
+ env: Environment;
43
+ target?: string;
44
+ command: string[];
33
45
  }
34
46
  export interface PushOptions {
35
47
  root: string;
@@ -67,6 +79,11 @@ export interface CheckResult {
67
79
  status: 'ok' | 'drift' | 'error';
68
80
  files: CheckFileResult[];
69
81
  }
82
+ export interface SkillOptions {
83
+ root: string;
84
+ global?: boolean;
85
+ local?: boolean;
86
+ }
70
87
  export declare const DEFAULT_SOURCES: SourceFiles;
71
88
  export declare const FORMAT_OUTPUT_FILES: Record<OutputFormat, Record<Environment, string>>;
72
89
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AACpE,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC;CAChD;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAE9G,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;IACjC,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,eAAe,EAAE,WAI7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAiBjF,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -2,6 +2,7 @@ export const DEFAULT_SOURCES = {
2
2
  shared: '.env',
3
3
  development: '.env.development',
4
4
  production: '.env.production',
5
+ local: '.env.local',
5
6
  };
6
7
  export const FORMAT_OUTPUT_FILES = {
7
8
  dotenv: {
@@ -20,4 +21,8 @@ export const FORMAT_OUTPUT_FILES = {
20
21
  development: '.env.development.sh',
21
22
  production: '.env.production.sh',
22
23
  },
24
+ yaml: {
25
+ development: '.env.development.yaml',
26
+ production: '.env.production.yaml',
27
+ },
23
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chriscode/hush",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "SOPS-based secrets management for monorepos. Encrypt once, decrypt everywhere.",
5
5
  "type": "module",
6
6
  "bin": {