@chriscode/hush 4.1.1 → 4.1.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.
package/dist/cli.js
CHANGED
|
@@ -79,6 +79,11 @@ ${pc.bold('Variable Expansion (v4+):')}
|
|
|
79
79
|
\${VAR:-default} Pull VAR, use default if missing
|
|
80
80
|
\${env:VAR} Read from system environment (CI, etc.)
|
|
81
81
|
|
|
82
|
+
Behavior:
|
|
83
|
+
- Template vars are merged with target filters (additive)
|
|
84
|
+
- Template vars take precedence over target filters
|
|
85
|
+
- Subdirectory templates are safe to commit
|
|
86
|
+
|
|
82
87
|
Example subdirectory template (apps/mobile/.env):
|
|
83
88
|
EXPO_PUBLIC_API_URL=\${API_URL}
|
|
84
89
|
PORT=\${PORT:-3000}
|
|
@@ -90,8 +95,8 @@ ${pc.bold('Examples:')}
|
|
|
90
95
|
hush encrypt Encrypt .env files
|
|
91
96
|
hush run -- npm start Run with secrets in memory (AI-safe!)
|
|
92
97
|
hush run -e prod -- npm build Run with production secrets
|
|
93
|
-
hush run -t api -- wrangler dev Run filtered for 'api' target
|
|
94
|
-
cd apps/mobile && hush run -- expo start Run from subdirectory
|
|
98
|
+
hush run -t api -- wrangler dev Run filtered for 'api' target (root secrets only)
|
|
99
|
+
cd apps/mobile && hush run -- expo start Run from subdirectory (applies template + target filters)
|
|
95
100
|
hush set DATABASE_URL Set a secret interactively (AI-safe)
|
|
96
101
|
hush set API_KEY --gui Set secret via macOS dialog (for AI agents)
|
|
97
102
|
hush set API_KEY -e prod Set a production secret
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/commands/resolve.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/commands/resolve.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAU,WAAW,EAAU,MAAM,aAAa,CAAC;AAG/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AA6BD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmI3E"}
|
package/dist/commands/resolve.js
CHANGED
|
@@ -6,6 +6,7 @@ import { interpolateVars } from '../core/interpolate.js';
|
|
|
6
6
|
import { mergeVars } from '../core/merge.js';
|
|
7
7
|
import { parseEnvContent } from '../core/parse.js';
|
|
8
8
|
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
9
|
+
import { loadLocalTemplates } from '../core/template.js';
|
|
9
10
|
import { FORMAT_OUTPUT_FILES } from '../types.js';
|
|
10
11
|
function matchesPattern(key, pattern) {
|
|
11
12
|
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
@@ -87,7 +88,7 @@ export async function resolveCommand(options) {
|
|
|
87
88
|
console.log(`Path: ${pc.dim(target.path + '/')}`);
|
|
88
89
|
console.log(`Format: ${pc.dim(target.format)} ${pc.dim(`(${outputFile})`)}`);
|
|
89
90
|
console.log(`Environment: ${pc.dim(env)}`);
|
|
90
|
-
console.log(pc.green(`\n✅
|
|
91
|
+
console.log(pc.green(`\n✅ ROOT SECRETS (Matched Filters) (${included.length}):`));
|
|
91
92
|
if (included.length === 0) {
|
|
92
93
|
console.log(pc.dim(' (none)'));
|
|
93
94
|
}
|
|
@@ -107,5 +108,33 @@ export async function resolveCommand(options) {
|
|
|
107
108
|
console.log(` ${v.key.padEnd(maxKeyLen)} ${pc.dim(`(matches: ${v.pattern})`)}`);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
111
|
+
const targetAbsPath = join(root, target.path);
|
|
112
|
+
const localTemplate = loadLocalTemplates(targetAbsPath, env);
|
|
113
|
+
if (localTemplate.hasTemplate) {
|
|
114
|
+
console.log(pc.blue(`\n📄 TEMPLATE EXPANSIONS (${pc.dim(join(target.path, '.env'))}):`));
|
|
115
|
+
const maxKeyLen = Math.max(...localTemplate.vars.map(v => v.key.length));
|
|
116
|
+
for (const v of localTemplate.vars) {
|
|
117
|
+
console.log(` ${v.key.padEnd(maxKeyLen)} ${pc.dim(`← ${v.value}`)}`);
|
|
118
|
+
}
|
|
119
|
+
// Calculate final merged list for clarity
|
|
120
|
+
const finalKeys = new Set([
|
|
121
|
+
...included.map(v => v.key),
|
|
122
|
+
...localTemplate.vars.map(v => v.key)
|
|
123
|
+
]);
|
|
124
|
+
console.log(pc.magenta(`\n📦 FINAL INJECTION (${finalKeys.size} total):`));
|
|
125
|
+
const sortedKeys = Array.from(finalKeys).sort();
|
|
126
|
+
for (const key of sortedKeys) {
|
|
127
|
+
const isTemplate = localTemplate.vars.some(v => v.key === key);
|
|
128
|
+
const isRoot = included.some(v => v.key === key);
|
|
129
|
+
let sourceInfo = '';
|
|
130
|
+
if (isTemplate && isRoot)
|
|
131
|
+
sourceInfo = pc.dim('(template overrides root)');
|
|
132
|
+
else if (isTemplate)
|
|
133
|
+
sourceInfo = pc.dim('(template)');
|
|
134
|
+
else if (isRoot)
|
|
135
|
+
sourceInfo = pc.dim('(root)');
|
|
136
|
+
console.log(` ${key} ${sourceInfo}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
110
139
|
console.log('');
|
|
111
140
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAmC,MAAM,aAAa,CAAC;AAkD/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAmC,MAAM,aAAa,CAAC;AAkD/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8GnE"}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
4
|
import pc from 'picocolors';
|
|
5
5
|
import { loadConfig, findProjectRoot } from '../config/loader.js';
|
|
6
6
|
import { filterVarsForTarget } from '../core/filter.js';
|
|
@@ -67,20 +67,26 @@ export async function runCommand(options) {
|
|
|
67
67
|
const rootSecrets = getDecryptedSecrets(projectRoot, env, config);
|
|
68
68
|
const rootSecretsRecord = getRootSecretsAsRecord(rootSecrets);
|
|
69
69
|
const localTemplate = loadLocalTemplates(contextDir, env);
|
|
70
|
-
|
|
70
|
+
// 1. Resolve Template Vars
|
|
71
|
+
let templateVars = [];
|
|
71
72
|
if (localTemplate.hasTemplate) {
|
|
72
|
-
|
|
73
|
+
templateVars = resolveTemplateVars(localTemplate.vars, rootSecretsRecord, { processEnv: process.env });
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
// 2. Resolve Target Vars
|
|
76
|
+
let targetVars = [];
|
|
77
|
+
// Find target config: either explicit by name, or implicit by directory matching
|
|
78
|
+
const targetConfig = target
|
|
79
|
+
? config.targets.find(t => t.name === target)
|
|
80
|
+
: config.targets.find(t => resolve(projectRoot, t.path) === resolve(contextDir));
|
|
81
|
+
if (target && !targetConfig) {
|
|
82
|
+
console.error(pc.red(`Target "${target}" not found in hush.yaml`));
|
|
83
|
+
console.error(pc.dim(`Available targets: ${config.targets.map(t => t.name).join(', ')}`));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (targetConfig) {
|
|
87
|
+
targetVars = filterVarsForTarget(rootSecrets, targetConfig);
|
|
82
88
|
if (targetConfig.format === 'wrangler') {
|
|
83
|
-
|
|
89
|
+
targetVars.push({ key: 'CLOUDFLARE_INCLUDE_PROCESS_ENV', value: 'true' });
|
|
84
90
|
const devVarsPath = join(targetConfig.path, '.dev.vars');
|
|
85
91
|
const absDevVarsPath = join(projectRoot, devVarsPath);
|
|
86
92
|
if (existsSync(absDevVarsPath)) {
|
|
@@ -91,8 +97,21 @@ export async function runCommand(options) {
|
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
}
|
|
100
|
+
else if (!localTemplate.hasTemplate && !target) {
|
|
101
|
+
// If no template and no target matched (and not running explicit target), fallback to all secrets
|
|
102
|
+
// This maintains backward compatibility for running in root or non-target dirs without templates
|
|
103
|
+
targetVars = rootSecrets;
|
|
104
|
+
}
|
|
105
|
+
// 3. Merge (Template overrides Target)
|
|
106
|
+
let vars;
|
|
107
|
+
if (localTemplate.hasTemplate) {
|
|
108
|
+
// Merge target vars with template vars.
|
|
109
|
+
// Template vars take precedence over target vars.
|
|
110
|
+
// This allows "additive" behavior: get target vars + template vars.
|
|
111
|
+
vars = mergeVars(targetVars, templateVars);
|
|
112
|
+
}
|
|
94
113
|
else {
|
|
95
|
-
vars =
|
|
114
|
+
vars = targetVars;
|
|
96
115
|
}
|
|
97
116
|
const unresolved = getUnresolvedVars(vars);
|
|
98
117
|
if (unresolved.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAy+ChD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvE"}
|
package/dist/commands/skill.js
CHANGED
|
@@ -155,7 +155,9 @@ Hush automatically:
|
|
|
155
155
|
2. Decrypts root secrets
|
|
156
156
|
3. Loads the local \`.env\` template
|
|
157
157
|
4. Resolves \`\${VAR}\` references against root secrets
|
|
158
|
-
5.
|
|
158
|
+
5. **Filters root secrets based on target config (include/exclude)**
|
|
159
|
+
6. **Merges them (Template overrides Target)**
|
|
160
|
+
7. Injects the result into your command
|
|
159
161
|
|
|
160
162
|
### Variable Expansion Syntax
|
|
161
163
|
|