@chriscode/hush 5.0.0 → 5.0.2

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 (77) hide show
  1. package/dist/cli.js +39 -26
  2. package/dist/commands/check.d.ts +3 -3
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +27 -31
  5. package/dist/commands/decrypt.d.ts +2 -2
  6. package/dist/commands/decrypt.d.ts.map +1 -1
  7. package/dist/commands/decrypt.js +52 -55
  8. package/dist/commands/edit.d.ts +2 -2
  9. package/dist/commands/edit.d.ts.map +1 -1
  10. package/dist/commands/edit.js +10 -12
  11. package/dist/commands/encrypt.d.ts +2 -2
  12. package/dist/commands/encrypt.d.ts.map +1 -1
  13. package/dist/commands/encrypt.js +27 -29
  14. package/dist/commands/expansions.d.ts +2 -2
  15. package/dist/commands/expansions.d.ts.map +1 -1
  16. package/dist/commands/expansions.js +46 -44
  17. package/dist/commands/has.d.ts +2 -2
  18. package/dist/commands/has.d.ts.map +1 -1
  19. package/dist/commands/has.js +12 -15
  20. package/dist/commands/init.d.ts +2 -2
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +92 -100
  23. package/dist/commands/inspect.d.ts +2 -2
  24. package/dist/commands/inspect.d.ts.map +1 -1
  25. package/dist/commands/inspect.js +14 -16
  26. package/dist/commands/keys.d.ts +2 -1
  27. package/dist/commands/keys.d.ts.map +1 -1
  28. package/dist/commands/keys.js +47 -49
  29. package/dist/commands/list.d.ts +2 -2
  30. package/dist/commands/list.d.ts.map +1 -1
  31. package/dist/commands/list.js +11 -14
  32. package/dist/commands/migrate.d.ts +2 -1
  33. package/dist/commands/migrate.d.ts.map +1 -1
  34. package/dist/commands/migrate.js +38 -37
  35. package/dist/commands/push.d.ts +2 -2
  36. package/dist/commands/push.d.ts.map +1 -1
  37. package/dist/commands/push.js +41 -45
  38. package/dist/commands/resolve.d.ts +2 -2
  39. package/dist/commands/resolve.d.ts.map +1 -1
  40. package/dist/commands/resolve.js +25 -28
  41. package/dist/commands/run.d.ts +2 -2
  42. package/dist/commands/run.d.ts.map +1 -1
  43. package/dist/commands/run.js +35 -39
  44. package/dist/commands/set.d.ts +2 -2
  45. package/dist/commands/set.d.ts.map +1 -1
  46. package/dist/commands/set.js +61 -70
  47. package/dist/commands/skill.d.ts +2 -2
  48. package/dist/commands/skill.d.ts.map +1 -1
  49. package/dist/commands/skill.js +149 -459
  50. package/dist/commands/status.d.ts +2 -2
  51. package/dist/commands/status.d.ts.map +1 -1
  52. package/dist/commands/status.js +48 -52
  53. package/dist/commands/template.d.ts +2 -2
  54. package/dist/commands/template.d.ts.map +1 -1
  55. package/dist/commands/template.js +36 -39
  56. package/dist/commands/trace.d.ts +2 -2
  57. package/dist/commands/trace.d.ts.map +1 -1
  58. package/dist/commands/trace.js +16 -19
  59. package/dist/config/loader.js +3 -3
  60. package/dist/context.d.ts +3 -0
  61. package/dist/context.d.ts.map +1 -0
  62. package/dist/context.js +60 -0
  63. package/dist/core/parse.js +3 -3
  64. package/dist/core/sops.js +9 -9
  65. package/dist/core/template.d.ts +2 -2
  66. package/dist/core/template.d.ts.map +1 -1
  67. package/dist/core/template.js +11 -12
  68. package/dist/lib/age.js +9 -9
  69. package/dist/lib/fs.d.ts +25 -0
  70. package/dist/lib/fs.d.ts.map +1 -0
  71. package/dist/lib/fs.js +36 -0
  72. package/dist/lib/onepassword.d.ts.map +1 -1
  73. package/dist/lib/onepassword.js +41 -4
  74. package/dist/types.d.ts +92 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/utils/version-check.js +5 -5
  77. package/package.json +3 -2
@@ -1,17 +1,12 @@
1
- import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
1
  import pc from 'picocolors';
4
2
  import { stringify as stringifyYaml } from 'yaml';
5
- import { findConfigPath } from '../config/loader.js';
6
- import { ageAvailable, ageGenerate, keyExists, keySave, keyPath } from '../lib/age.js';
7
- import { opAvailable, opGetKey, opStoreKey } from '../lib/onepassword.js';
8
3
  import { DEFAULT_SOURCES } from '../types.js';
9
- function getProjectFromPackageJson(root) {
10
- const pkgPath = join(root, 'package.json');
11
- if (!existsSync(pkgPath))
4
+ function getProjectFromPackageJson(ctx, root) {
5
+ const pkgPath = ctx.path.join(root, 'package.json');
6
+ if (!ctx.fs.existsSync(pkgPath))
12
7
  return null;
13
8
  try {
14
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
9
+ const pkg = JSON.parse(ctx.fs.readFileSync(pkgPath, 'utf-8'));
15
10
  if (typeof pkg.repository === 'string') {
16
11
  const match = pkg.repository.match(/github\.com[/:]([\w-]+\/[\w-]+)/);
17
12
  if (match)
@@ -28,86 +23,85 @@ function getProjectFromPackageJson(root) {
28
23
  }
29
24
  return null;
30
25
  }
31
- async function tryExistingLocalKey(project) {
32
- if (!keyExists(project))
26
+ async function tryExistingLocalKey(ctx, project) {
27
+ if (!ctx.age.keyExists(project))
33
28
  return null;
34
- const existing = await import('../lib/age.js').then(m => m.keyLoad(project));
29
+ const existing = await ctx.age.keyLoad(project);
35
30
  if (!existing)
36
31
  return null;
37
- console.log(pc.green(`Using existing key for ${pc.cyan(project)}`));
32
+ ctx.logger.log(pc.green(`Using existing key for ${pc.cyan(project)}`));
38
33
  return { publicKey: existing.public, source: 'existing' };
39
34
  }
40
- async function tryPullFrom1Password(project) {
41
- if (!opAvailable())
35
+ async function tryPullFrom1Password(ctx, project) {
36
+ if (!ctx.onepassword.opAvailable())
42
37
  return null;
43
- console.log(pc.dim('Checking 1Password for existing key...'));
44
- const priv = opGetKey(project);
38
+ ctx.logger.log(pc.dim('Checking 1Password for existing key...'));
39
+ const priv = ctx.onepassword.opGetKey(project);
45
40
  if (!priv)
46
41
  return null;
47
- const { agePublicFromPrivate } = await import('../lib/age.js');
48
- const pub = agePublicFromPrivate(priv);
49
- keySave(project, { private: priv, public: pub });
50
- console.log(pc.green(`Pulled key from 1Password for ${pc.cyan(project)}`));
42
+ const pub = ctx.age.agePublicFromPrivate(priv);
43
+ ctx.age.keySave(project, { private: priv, public: pub });
44
+ ctx.logger.log(pc.green(`Pulled key from 1Password for ${pc.cyan(project)}`));
51
45
  return { publicKey: pub, source: '1password' };
52
46
  }
53
- function generateAndBackupKey(project) {
54
- if (!ageAvailable()) {
55
- console.log(pc.yellow('age not installed. Run: brew install age'));
47
+ function generateAndBackupKey(ctx, project) {
48
+ if (!ctx.age.ageAvailable()) {
49
+ ctx.logger.log(pc.yellow('age not installed. Run: brew install age'));
56
50
  return null;
57
51
  }
58
- console.log(pc.blue(`Generating new key for ${pc.cyan(project)}...`));
59
- const key = ageGenerate();
60
- keySave(project, key);
61
- console.log(pc.green(`Saved to ${keyPath(project)}`));
62
- console.log(pc.dim(`Public: ${key.public}`));
63
- if (opAvailable()) {
52
+ ctx.logger.log(pc.blue(`Generating new key for ${pc.cyan(project)}...`));
53
+ const key = ctx.age.ageGenerate();
54
+ ctx.age.keySave(project, key);
55
+ ctx.logger.log(pc.green(`Saved to ${ctx.age.keyPath(project)}`));
56
+ ctx.logger.log(pc.dim(`Public: ${key.public}`));
57
+ if (ctx.onepassword.opAvailable()) {
64
58
  try {
65
- opStoreKey(project, key.private, key.public);
66
- console.log(pc.green('Backed up to 1Password.'));
59
+ ctx.onepassword.opStoreKey(project, key.private, key.public);
60
+ ctx.logger.log(pc.green('Backed up to 1Password.'));
67
61
  }
68
62
  catch (e) {
69
- console.warn(pc.yellow(`Could not backup to 1Password: ${e.message}`));
63
+ ctx.logger.warn(pc.yellow(`Could not backup to 1Password: ${e.message}`));
70
64
  }
71
65
  }
72
66
  return { publicKey: key.public, source: 'generated' };
73
67
  }
74
- async function setupKey(root, project) {
68
+ async function setupKey(ctx, root, project) {
75
69
  if (!project) {
76
- console.log(pc.yellow('No project identifier found. Skipping key setup.'));
77
- console.log(pc.dim('Add "project: my-project" to hush.yaml or set repository in package.json'));
70
+ ctx.logger.log(pc.yellow('No project identifier found. Skipping key setup.'));
71
+ ctx.logger.log(pc.dim('Add "project: my-org/my-repo" to hush.yaml or set repository in package.json'));
78
72
  return null;
79
73
  }
80
- return ((await tryExistingLocalKey(project)) ||
81
- (await tryPullFrom1Password(project)) ||
82
- generateAndBackupKey(project));
74
+ return ((await tryExistingLocalKey(ctx, project)) ||
75
+ (await tryPullFrom1Password(ctx, project)) ||
76
+ generateAndBackupKey(ctx, project));
83
77
  }
84
- function createSopsConfig(root, publicKey) {
85
- const sopsPath = join(root, '.sops.yaml');
86
- if (existsSync(sopsPath)) {
87
- console.log(pc.yellow('.sops.yaml already exists. Add this public key if needed:'));
88
- console.log(` ${publicKey}`);
78
+ function createSopsConfig(ctx, root, publicKey) {
79
+ const sopsPath = ctx.path.join(root, '.sops.yaml');
80
+ if (ctx.fs.existsSync(sopsPath)) {
81
+ ctx.logger.log(pc.yellow('.sops.yaml already exists. Add this public key if needed:'));
82
+ ctx.logger.log(` ${publicKey}`);
89
83
  return;
90
84
  }
91
85
  const sopsConfig = stringifyYaml({
92
86
  creation_rules: [{ encrypted_regex: '.*', age: publicKey }]
93
87
  });
94
- writeFileSync(sopsPath, sopsConfig, 'utf-8');
95
- console.log(pc.green('Created .sops.yaml'));
88
+ ctx.fs.writeFileSync(sopsPath, sopsConfig, 'utf-8');
89
+ ctx.logger.log(pc.green('Created .sops.yaml'));
96
90
  }
97
- function detectTargets(root) {
91
+ function detectTargets(ctx, root) {
98
92
  const targets = [{ name: 'root', path: '.', format: 'dotenv' }];
99
- const entries = readdirSync(root, { withFileTypes: true });
93
+ const entries = ctx.fs.readdirSync(root, { withFileTypes: true });
100
94
  for (const entry of entries) {
101
95
  if (!entry.isDirectory())
102
96
  continue;
103
97
  if (entry.name.startsWith('.') || entry.name === 'node_modules')
104
98
  continue;
105
- const dirPath = join(root, entry.name);
106
- const packageJsonPath = join(dirPath, 'package.json');
107
- const wranglerPath = join(dirPath, 'wrangler.toml');
108
- if (!existsSync(packageJsonPath))
99
+ const dirPath = ctx.path.join(root, entry.name);
100
+ const packageJsonPath = ctx.path.join(dirPath, 'package.json');
101
+ const wranglerPath = ctx.path.join(dirPath, 'wrangler.toml');
102
+ if (!ctx.fs.existsSync(packageJsonPath))
109
103
  continue;
110
- if (existsSync(wranglerPath)) {
104
+ if (ctx.fs.existsSync(wranglerPath)) {
111
105
  targets.push({
112
106
  name: entry.name,
113
107
  path: `./${entry.name}`,
@@ -125,67 +119,65 @@ function detectTargets(root) {
125
119
  }
126
120
  return targets;
127
121
  }
128
- function findExistingPlaintextEnvFiles(root) {
129
- // Look for legacy .env files that may need migration
122
+ function findExistingPlaintextEnvFiles(ctx, root) {
130
123
  const patterns = ['.env', '.env.development', '.env.production', '.env.local', '.env.staging', '.env.test', '.dev.vars'];
131
124
  const found = [];
132
125
  for (const pattern of patterns) {
133
- const filePath = join(root, pattern);
134
- if (existsSync(filePath)) {
126
+ const filePath = ctx.path.join(root, pattern);
127
+ if (ctx.fs.existsSync(filePath)) {
135
128
  found.push(pattern);
136
129
  }
137
130
  }
138
131
  return found;
139
132
  }
140
- function findExistingEncryptedFiles(root) {
141
- // Look for v4 encrypted files that need migration to v5 (.hush.encrypted)
133
+ function findExistingEncryptedFiles(ctx, root) {
142
134
  const patterns = ['.env.encrypted', '.env.development.encrypted', '.env.production.encrypted', '.env.local.encrypted'];
143
135
  const found = [];
144
136
  for (const pattern of patterns) {
145
- const filePath = join(root, pattern);
146
- if (existsSync(filePath)) {
137
+ const filePath = ctx.path.join(root, pattern);
138
+ if (ctx.fs.existsSync(filePath)) {
147
139
  found.push(pattern);
148
140
  }
149
141
  }
150
142
  return found;
151
143
  }
152
- export async function initCommand(options) {
144
+ export async function initCommand(ctx, options) {
153
145
  const { root } = options;
154
- const existingConfig = findConfigPath(root);
146
+ const existingConfig = ctx.config.findProjectRoot(root);
155
147
  if (existingConfig) {
156
- console.error(pc.red(`Config already exists: ${existingConfig}`));
157
- process.exit(1);
148
+ ctx.logger.error(pc.red(`Config already exists: ${existingConfig.configPath}`));
149
+ ctx.process.exit(1);
158
150
  }
159
- console.log(pc.blue('Initializing hush...\n'));
160
- const existingEncryptedFiles = findExistingEncryptedFiles(root);
151
+ ctx.logger.log(pc.blue('Initializing hush...\n'));
152
+ const existingEncryptedFiles = findExistingEncryptedFiles(ctx, root);
161
153
  if (existingEncryptedFiles.length > 0) {
162
- console.log(pc.bgYellow(pc.black(' V4 ENCRYPTED FILES DETECTED ')));
163
- console.log(pc.yellow('\nFound existing v4 encrypted files:'));
154
+ ctx.logger.log(pc.bgYellow(pc.black(' V4 ENCRYPTED FILES DETECTED ')));
155
+ ctx.logger.log(pc.yellow('\nFound existing v4 encrypted files:'));
164
156
  for (const file of existingEncryptedFiles) {
165
- console.log(pc.yellow(` ${file}`));
157
+ ctx.logger.log(pc.yellow(` ${file}`));
166
158
  }
167
- console.log(pc.dim('\nRun "npx hush migrate" to convert to v5 format (.hush.encrypted).\n'));
159
+ ctx.logger.log(pc.dim('\nRun "npx hush migrate" to convert to v5 format (.hush.encrypted).\n'));
168
160
  }
169
- const existingEnvFiles = findExistingPlaintextEnvFiles(root);
161
+ const existingEnvFiles = findExistingPlaintextEnvFiles(ctx, root);
170
162
  if (existingEnvFiles.length > 0) {
171
- console.log(pc.bgYellow(pc.black(' PLAINTEXT .ENV FILES DETECTED ')));
172
- console.log(pc.yellow('\nFound existing .env files:'));
163
+ ctx.logger.log(pc.bgYellow(pc.black(' PLAINTEXT .ENV FILES DETECTED ')));
164
+ ctx.logger.log(pc.yellow('\nFound existing .env files:'));
173
165
  for (const file of existingEnvFiles) {
174
- console.log(pc.yellow(` ${file}`));
166
+ ctx.logger.log(pc.yellow(` ${file}`));
175
167
  }
176
- console.log(pc.dim('\nRename these to .hush files, then run "npx hush encrypt".\n'));
177
- console.log(pc.dim('Example: mv .env .hush && mv .env.development .hush.development\n'));
168
+ ctx.logger.log(pc.dim('\nRename these to .hush files, then run "npx hush encrypt".\n'));
169
+ ctx.logger.log(pc.dim('Example: mv .env .hush && mv .env.development .hush.development\n'));
178
170
  }
179
- const project = getProjectFromPackageJson(root);
171
+ const project = getProjectFromPackageJson(ctx, root);
180
172
  if (!project) {
181
- console.log(pc.yellow('No project identifier found in package.json.'));
182
- console.log(pc.dim('Tip: Add "project: my-org/my-repo" to hush.yaml after creation for key management.\n'));
173
+ ctx.logger.log(pc.yellow('No project identifier found in package.json.'));
174
+ ctx.logger.log(pc.dim('Tip: Add "project: my-org/my-repo" to hush.yaml after creation for key management.\n'));
183
175
  }
184
- const keyResult = await setupKey(root, project);
176
+ const keyResult = await setupKey(ctx, root, project);
185
177
  if (keyResult) {
186
- createSopsConfig(root, keyResult.publicKey);
178
+ createSopsConfig(ctx, root, keyResult.publicKey);
187
179
  }
188
- const targets = detectTargets(root);
180
+ const targets = detectTargets(ctx, root);
189
181
  const config = {
190
182
  version: 2,
191
183
  sources: DEFAULT_SOURCES,
@@ -194,29 +186,29 @@ export async function initCommand(options) {
194
186
  };
195
187
  const yaml = stringifyYaml(config, { indent: 2 });
196
188
  const schemaComment = '# yaml-language-server: $schema=https://unpkg.com/@chriscode/hush/schema.json\n';
197
- const configPath = join(root, 'hush.yaml');
198
- writeFileSync(configPath, schemaComment + yaml, 'utf-8');
199
- console.log(pc.green(`\nCreated ${configPath}`));
200
- console.log(pc.dim('\nDetected targets:'));
189
+ const configPath = ctx.path.join(root, 'hush.yaml');
190
+ ctx.fs.writeFileSync(configPath, schemaComment + yaml, 'utf-8');
191
+ ctx.logger.log(pc.green(`\nCreated ${configPath}`));
192
+ ctx.logger.log(pc.dim('\nDetected targets:'));
201
193
  for (const target of targets) {
202
- console.log(` ${pc.cyan(target.name)} ${pc.dim(target.path)} ${pc.magenta(target.format)}`);
194
+ ctx.logger.log(` ${pc.cyan(target.name)} ${pc.dim(target.path)} ${pc.magenta(target.format)}`);
203
195
  }
204
- console.log(pc.bold('\nNext steps:'));
196
+ ctx.logger.log(pc.bold('\nNext steps:'));
205
197
  if (existingEncryptedFiles.length > 0) {
206
- console.log(pc.green(' 1. npx hush migrate') + pc.dim(' # Convert v4 .env.encrypted to v5 .hush.encrypted'));
207
- console.log(pc.dim(' 2. npx hush inspect') + pc.dim(' # Verify your secrets'));
208
- console.log(pc.dim(' 3. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
198
+ ctx.logger.log(pc.green(' 1. npx hush migrate') + pc.dim(' # Convert v4 .env.encrypted to v5 .hush.encrypted'));
199
+ ctx.logger.log(pc.dim(' 2. npx hush inspect') + pc.dim(' # Verify your secrets'));
200
+ ctx.logger.log(pc.dim(' 3. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
209
201
  }
210
202
  else if (existingEnvFiles.length > 0) {
211
- console.log(pc.green(' 1. Rename .env files to .hush') + pc.dim(' # mv .env .hush'));
212
- console.log(pc.dim(' 2. npx hush encrypt') + pc.dim(' # Encrypt .hush files'));
213
- console.log(pc.dim(' 3. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
203
+ ctx.logger.log(pc.green(' 1. Rename .env files to .hush') + pc.dim(' # mv .env .hush'));
204
+ ctx.logger.log(pc.dim(' 2. npx hush encrypt') + pc.dim(' # Encrypt .hush files'));
205
+ ctx.logger.log(pc.dim(' 3. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
214
206
  }
215
207
  else {
216
- console.log(pc.dim(' 1. npx hush set <KEY>') + pc.dim(' # Add secrets interactively'));
217
- console.log(pc.dim(' 2. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
208
+ ctx.logger.log(pc.dim(' 1. npx hush set <KEY>') + pc.dim(' # Add secrets interactively'));
209
+ ctx.logger.log(pc.dim(' 2. npx hush run -- <cmd>') + pc.dim(' # Run with secrets in memory'));
218
210
  }
219
- console.log(pc.dim('\nGit setup:'));
220
- console.log(pc.dim(' git add hush.yaml .sops.yaml'));
221
- console.log(pc.dim(' git commit -m "chore: add Hush secrets management"'));
211
+ ctx.logger.log(pc.dim('\nGit setup:'));
212
+ ctx.logger.log(pc.dim(' git add hush.yaml .sops.yaml'));
213
+ ctx.logger.log(pc.dim(' git commit -m "chore: add Hush secrets management"'));
222
214
  }
@@ -1,7 +1,7 @@
1
- import type { Environment } from '../types.js';
1
+ import type { Environment, HushContext } from '../types.js';
2
2
  export interface InspectOptions {
3
3
  root: string;
4
4
  env: Environment;
5
5
  }
6
- export declare function inspectCommand(options: InspectOptions): Promise<void>;
6
+ export declare function inspectCommand(ctx: HushContext, options: InspectOptions): Promise<void>;
7
7
  //# sourceMappingURL=inspect.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD7F"}
@@ -1,25 +1,23 @@
1
- import { existsSync } from 'node:fs';
1
+ import { fs } from '../lib/fs.js';
2
2
  import { join } from 'node:path';
3
3
  import pc from 'picocolors';
4
- import { loadConfig } from '../config/loader.js';
5
4
  import { filterVarsForTarget, describeFilter } from '../core/filter.js';
6
5
  import { interpolateVars } from '../core/interpolate.js';
7
6
  import { maskVars, formatMaskedVar } from '../core/mask.js';
8
7
  import { mergeVars } from '../core/merge.js';
9
8
  import { parseEnvContent } from '../core/parse.js';
10
- import { decrypt as sopsDecrypt } from '../core/sops.js';
11
- export async function inspectCommand(options) {
9
+ export async function inspectCommand(ctx, options) {
12
10
  const { root, env } = options;
13
- const config = loadConfig(root);
11
+ const config = ctx.config.loadConfig(root);
14
12
  const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
15
13
  const envEncrypted = join(root, config.sources[env] + '.encrypted');
16
14
  const varSources = [];
17
- if (existsSync(sharedEncrypted)) {
18
- const content = sopsDecrypt(sharedEncrypted);
15
+ if (fs.existsSync(sharedEncrypted)) {
16
+ const content = ctx.sops.decrypt(sharedEncrypted);
19
17
  varSources.push(parseEnvContent(content));
20
18
  }
21
- if (existsSync(envEncrypted)) {
22
- const content = sopsDecrypt(envEncrypted);
19
+ if (fs.existsSync(envEncrypted)) {
20
+ const content = ctx.sops.decrypt(envEncrypted);
23
21
  varSources.push(parseEnvContent(content));
24
22
  }
25
23
  if (varSources.length === 0) {
@@ -31,20 +29,20 @@ export async function inspectCommand(options) {
31
29
  const interpolated = interpolateVars(merged);
32
30
  const masked = maskVars(interpolated);
33
31
  const maxKeyLen = Math.max(...masked.map(v => v.key.length));
34
- console.log(pc.blue(`\nSecrets for ${env}:\n`));
32
+ ctx.logger.log(pc.blue(`\nSecrets for ${env}:\n`));
35
33
  for (const v of masked) {
36
34
  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)')}`);
35
+ ctx.logger.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
36
  }
39
- console.log(pc.dim(`\nTotal: ${masked.length} variables\n`));
40
- console.log(pc.blue('Target distribution:\n'));
37
+ ctx.logger.log(pc.dim(`\nTotal: ${masked.length} variables\n`));
38
+ ctx.logger.log(pc.blue('Target distribution:\n'));
41
39
  for (const target of config.targets) {
42
40
  const filtered = filterVarsForTarget(interpolated, target);
43
41
  const filter = describeFilter(target);
44
- console.log(` ${pc.cyan(target.name)} ${pc.dim(`(${target.path}/)`)} - ${filtered.length} vars`);
42
+ ctx.logger.log(` ${pc.cyan(target.name)} ${pc.dim(`(${target.path}/)`)} - ${filtered.length} vars`);
45
43
  if (filter !== 'all vars') {
46
- console.log(` ${pc.dim(filter)}`);
44
+ ctx.logger.log(` ${pc.dim(filter)}`);
47
45
  }
48
46
  }
49
- console.log('');
47
+ ctx.logger.log('');
50
48
  }
@@ -1,8 +1,9 @@
1
+ import { HushContext } from '../types.js';
1
2
  export interface KeysOptions {
2
3
  root: string;
3
4
  subcommand: string;
4
5
  vault?: string;
5
6
  force?: boolean;
6
7
  }
7
- export declare function keysCommand(options: KeysOptions): Promise<void>;
8
+ export declare function keysCommand(ctx: HushContext, options: KeysOptions): Promise<void>;
8
9
  //# sourceMappingURL=keys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6HrE"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwBD,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6HvF"}
@@ -1,17 +1,15 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
1
  import { join } from 'node:path';
3
2
  import pc from 'picocolors';
4
3
  import { stringify as yamlStringify } from 'yaml';
5
- import { loadConfig } from '../config/loader.js';
6
4
  import { opAvailable, opGetKey, opStoreKey, opListKeys } from '../lib/onepassword.js';
7
5
  import { ageAvailable, ageGenerate, agePublicFromPrivate, keyExists, keySave, keyLoad, keysList, keyPath } from '../lib/age.js';
8
- function getProject(root) {
9
- const config = loadConfig(root);
6
+ function getProject(ctx, root) {
7
+ const config = ctx.config.loadConfig(root);
10
8
  if (config.project)
11
9
  return config.project;
12
10
  const pkgPath = join(root, 'package.json');
13
- if (existsSync(pkgPath)) {
14
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
11
+ if (ctx.fs.existsSync(pkgPath)) {
12
+ const pkg = JSON.parse(ctx.fs.readFileSync(pkgPath, 'utf-8'));
15
13
  if (typeof pkg.repository === 'string') {
16
14
  const match = pkg.repository.match(/github\.com[/:]([\w-]+\/[\w-]+)/);
17
15
  if (match)
@@ -23,18 +21,18 @@ function getProject(root) {
23
21
  return match[1];
24
22
  }
25
23
  }
26
- console.error(pc.red('No project identifier found.'));
27
- console.error(pc.dim('Add "project: my-project" to hush.yaml'));
28
- process.exit(1);
24
+ ctx.logger.error(pc.red('No project identifier found.'));
25
+ ctx.logger.error(pc.dim('Add "project: my-project" to hush.yaml'));
26
+ ctx.process.exit(1);
29
27
  }
30
- export async function keysCommand(options) {
28
+ export async function keysCommand(ctx, options) {
31
29
  const { root, subcommand, vault, force } = options;
32
30
  switch (subcommand) {
33
31
  case 'setup': {
34
- const project = getProject(root);
35
- console.log(pc.blue(`Setting up keys for ${pc.cyan(project)}...`));
32
+ const project = getProject(ctx, root);
33
+ ctx.logger.log(pc.blue(`Setting up keys for ${pc.cyan(project)}...`));
36
34
  if (keyExists(project)) {
37
- console.log(pc.green('Key already exists locally.'));
35
+ ctx.logger.log(pc.green('Key already exists locally.'));
38
36
  return;
39
37
  }
40
38
  if (opAvailable()) {
@@ -42,95 +40,95 @@ export async function keysCommand(options) {
42
40
  if (priv) {
43
41
  const pub = agePublicFromPrivate(priv);
44
42
  keySave(project, { private: priv, public: pub });
45
- console.log(pc.green('Pulled key from 1Password.'));
43
+ ctx.logger.log(pc.green('Pulled key from 1Password.'));
46
44
  return;
47
45
  }
48
46
  }
49
- console.log(pc.yellow('No key found. Run "hush keys generate" to create one.'));
47
+ ctx.logger.log(pc.yellow('No key found. Run "hush keys generate" to create one.'));
50
48
  break;
51
49
  }
52
50
  case 'generate': {
53
51
  if (!ageAvailable()) {
54
- console.error(pc.red('age not installed. Run: brew install age'));
55
- process.exit(1);
52
+ ctx.logger.error(pc.red('age not installed. Run: brew install age'));
53
+ ctx.process.exit(1);
56
54
  }
57
- const project = getProject(root);
55
+ const project = getProject(ctx, root);
58
56
  if (keyExists(project) && !force) {
59
- console.error(pc.yellow(`Key exists for ${project}. Use --force to overwrite.`));
60
- process.exit(1);
57
+ ctx.logger.error(pc.yellow(`Key exists for ${project}. Use --force to overwrite.`));
58
+ ctx.process.exit(1);
61
59
  }
62
- console.log(pc.blue(`Generating key for ${pc.cyan(project)}...`));
60
+ ctx.logger.log(pc.blue(`Generating key for ${pc.cyan(project)}...`));
63
61
  const key = ageGenerate();
64
62
  keySave(project, key);
65
- console.log(pc.green(`Saved to ${keyPath(project)}`));
66
- console.log(pc.dim(`Public: ${key.public}`));
63
+ ctx.logger.log(pc.green(`Saved to ${keyPath(project)}`));
64
+ ctx.logger.log(pc.dim(`Public: ${key.public}`));
67
65
  if (opAvailable()) {
68
66
  try {
69
67
  opStoreKey(project, key.private, key.public, vault);
70
- console.log(pc.green('Stored in 1Password.'));
68
+ ctx.logger.log(pc.green('Stored in 1Password.'));
71
69
  }
72
70
  catch (e) {
73
- console.warn(pc.yellow(`Could not store in 1Password: ${e.message}`));
71
+ ctx.logger.warn(pc.yellow(`Could not store in 1Password: ${e.message}`));
74
72
  }
75
73
  }
76
74
  const sopsPath = join(root, '.sops.yaml');
77
- if (!existsSync(sopsPath)) {
78
- writeFileSync(sopsPath, yamlStringify({ creation_rules: [{ encrypted_regex: '.*', age: key.public }] }));
79
- console.log(pc.green('Created .sops.yaml'));
75
+ if (!ctx.fs.existsSync(sopsPath)) {
76
+ ctx.fs.writeFileSync(sopsPath, yamlStringify({ creation_rules: [{ encrypted_regex: '.*', age: key.public }] }));
77
+ ctx.logger.log(pc.green('Created .sops.yaml'));
80
78
  }
81
79
  else {
82
- console.log(pc.yellow('.sops.yaml exists. Add this public key:'));
83
- console.log(` ${key.public}`);
80
+ ctx.logger.log(pc.yellow('.sops.yaml exists. Add this public key:'));
81
+ ctx.logger.log(` ${key.public}`);
84
82
  }
85
83
  break;
86
84
  }
87
85
  case 'pull': {
88
86
  if (!opAvailable()) {
89
- console.error(pc.red('1Password CLI not available or not signed in.'));
90
- process.exit(1);
87
+ ctx.logger.error(pc.red('1Password CLI not available or not signed in.'));
88
+ ctx.process.exit(1);
91
89
  }
92
- const project = getProject(root);
90
+ const project = getProject(ctx, root);
93
91
  const priv = opGetKey(project, vault);
94
92
  if (!priv) {
95
- console.error(pc.red(`No key in 1Password for ${project}`));
96
- process.exit(1);
93
+ ctx.logger.error(pc.red(`No key in 1Password for ${project}`));
94
+ ctx.process.exit(1);
97
95
  }
98
96
  const pub = agePublicFromPrivate(priv);
99
97
  keySave(project, { private: priv, public: pub });
100
- console.log(pc.green(`Pulled and saved to ${keyPath(project)}`));
98
+ ctx.logger.log(pc.green(`Pulled and saved to ${keyPath(project)}`));
101
99
  break;
102
100
  }
103
101
  case 'push': {
104
102
  if (!opAvailable()) {
105
- console.error(pc.red('1Password CLI not available or not signed in.'));
106
- process.exit(1);
103
+ ctx.logger.error(pc.red('1Password CLI not available or not signed in.'));
104
+ ctx.process.exit(1);
107
105
  }
108
- const project = getProject(root);
106
+ const project = getProject(ctx, root);
109
107
  const key = keyLoad(project);
110
108
  if (!key) {
111
- console.error(pc.red(`No local key for ${project}`));
112
- process.exit(1);
109
+ ctx.logger.error(pc.red(`No local key for ${project}`));
110
+ ctx.process.exit(1);
113
111
  }
114
112
  opStoreKey(project, key.private, key.public, vault);
115
- console.log(pc.green('Pushed to 1Password.'));
113
+ ctx.logger.log(pc.green('Pushed to 1Password.'));
116
114
  break;
117
115
  }
118
116
  case 'list': {
119
- console.log(pc.blue('Local keys:'));
117
+ ctx.logger.log(pc.blue('Local keys:'));
120
118
  for (const k of keysList()) {
121
- console.log(` ${pc.cyan(k.project)} ${pc.dim(k.public.slice(0, 20))}...`);
119
+ ctx.logger.log(` ${pc.cyan(k.project)} ${pc.dim(k.public.slice(0, 20))}...`);
122
120
  }
123
121
  if (opAvailable()) {
124
- console.log(pc.blue('\n1Password keys:'));
122
+ ctx.logger.log(pc.blue('\n1Password keys:'));
125
123
  for (const project of opListKeys(vault)) {
126
- console.log(` ${pc.cyan(project)}`);
124
+ ctx.logger.log(` ${pc.cyan(project)}`);
127
125
  }
128
126
  }
129
127
  break;
130
128
  }
131
129
  default:
132
- console.error(pc.red(`Unknown: hush keys ${subcommand}`));
133
- console.log(pc.dim('Commands: setup, generate, pull, push, list'));
134
- process.exit(1);
130
+ ctx.logger.error(pc.red(`Unknown: hush keys ${subcommand}`));
131
+ ctx.logger.log(pc.dim('Commands: setup, generate, pull, push, list'));
132
+ ctx.process.exit(1);
135
133
  }
136
134
  }
@@ -1,3 +1,3 @@
1
- import type { ListOptions } from '../types.js';
2
- export declare function listCommand(options: ListOptions): Promise<void>;
1
+ import type { ListOptions, HushContext } from '../types.js';
2
+ export declare function listCommand(ctx: HushContext, options: ListOptions): Promise<void>;
3
3
  //# sourceMappingURL=list.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAU,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpE,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCvF"}
@@ -1,35 +1,32 @@
1
- import { existsSync } from 'node:fs';
2
1
  import { join } from 'node:path';
3
2
  import pc from 'picocolors';
4
- import { loadConfig } from '../config/loader.js';
5
3
  import { interpolateVars } from '../core/interpolate.js';
6
4
  import { mergeVars } from '../core/merge.js';
7
5
  import { parseEnvContent } from '../core/parse.js';
8
- import { decrypt as sopsDecrypt } from '../core/sops.js';
9
- export async function listCommand(options) {
6
+ export async function listCommand(ctx, options) {
10
7
  const { root, env } = options;
11
- const config = loadConfig(root);
12
- console.log(pc.blue(`Variables for ${env}:\n`));
8
+ const config = ctx.config.loadConfig(root);
9
+ ctx.logger.log(pc.blue(`Variables for ${env}:\n`));
13
10
  const sharedEncrypted = join(root, config.sources.shared + '.encrypted');
14
11
  const envEncrypted = join(root, config.sources[env] + '.encrypted');
15
12
  const varSources = [];
16
- if (existsSync(sharedEncrypted)) {
17
- const content = sopsDecrypt(sharedEncrypted);
13
+ if (ctx.fs.existsSync(sharedEncrypted)) {
14
+ const content = ctx.sops.decrypt(sharedEncrypted);
18
15
  varSources.push(parseEnvContent(content));
19
16
  }
20
- if (existsSync(envEncrypted)) {
21
- const content = sopsDecrypt(envEncrypted);
17
+ if (ctx.fs.existsSync(envEncrypted)) {
18
+ const content = ctx.sops.decrypt(envEncrypted);
22
19
  varSources.push(parseEnvContent(content));
23
20
  }
24
21
  if (varSources.length === 0) {
25
- console.error(pc.red('No encrypted files found'));
26
- process.exit(1);
22
+ ctx.logger.error(pc.red('No encrypted files found'));
23
+ ctx.process.exit(1);
27
24
  }
28
25
  const merged = mergeVars(...varSources);
29
26
  const interpolated = interpolateVars(merged);
30
27
  for (const { key, value } of interpolated) {
31
28
  const displayValue = value.length > 50 ? value.slice(0, 47) + '...' : value;
32
- console.log(`${pc.cyan(key)}=${pc.dim(displayValue)}`);
29
+ ctx.logger.log(`${pc.cyan(key)}=${pc.dim(displayValue)}`);
33
30
  }
34
- console.log(pc.dim(`\nTotal: ${interpolated.length} variables`));
31
+ ctx.logger.log(pc.dim(`\nTotal: ${interpolated.length} variables`));
35
32
  }