@saasak/tool-env 1.3.0 → 1.5.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/bin/index.js +164 -84
- package/package.json +1 -1
- package/src/core.js +21 -9
- package/src/index.d.ts +8 -0
- package/src/utils-env.js +18 -0
package/bin/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import fs from "fs-extra";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execa } from "execa";
|
|
7
7
|
|
|
8
|
-
import { encrypt, isEncrypted } from "../src/utils-crypto.js";
|
|
9
|
-
import { buildEnv } from "../src/utils-env.js";
|
|
8
|
+
import { encrypt, decrypt, isEncrypted } from "../src/utils-crypto.js";
|
|
9
|
+
import { buildEnv, walkEnvValues } from "../src/utils-env.js";
|
|
10
10
|
import { findMonorepoPackages } from "../src/utils-pkg.js";
|
|
11
11
|
import {
|
|
12
12
|
resolveTarget,
|
|
@@ -14,121 +14,148 @@ import {
|
|
|
14
14
|
verifyCanDecrypt,
|
|
15
15
|
getOutputName,
|
|
16
16
|
load,
|
|
17
|
+
findMonorepoRoot,
|
|
17
18
|
} from "../src/core.js";
|
|
18
19
|
|
|
19
|
-
const args = minimist(process.argv.slice(2), {
|
|
20
|
-
|
|
20
|
+
const args = minimist(process.argv.slice(2), {
|
|
21
|
+
"--": true,
|
|
22
|
+
boolean: ["strict", "expose", "help", "h"],
|
|
23
|
+
string: ["format", "package"],
|
|
24
|
+
});
|
|
25
|
+
const __cwd = process.cwd();
|
|
26
|
+
const envPath = args.env || ".env.json";
|
|
27
|
+
const __root = findMonorepoRoot(__cwd, envPath) || __cwd;
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
let command = args._[0] || "help";
|
|
30
|
+
if (args.help || args.h) command = "help";
|
|
23
31
|
|
|
24
32
|
const secret = resolveSecret(args.secret ? `file://${args.secret}` : '');
|
|
25
33
|
|
|
26
|
-
const envPath = args.env || ".env.json";
|
|
27
34
|
const envFile = path.resolve(__root, envPath);
|
|
28
35
|
|
|
29
36
|
// Check env file exists for commands that need it
|
|
30
|
-
const commandsRequiringEnvFile = ["write", "show", "encrypt", "besafe", "add"];
|
|
37
|
+
const commandsRequiringEnvFile = ["write", "show", "encrypt", "decrypt", "besafe", "add", "get"];
|
|
31
38
|
if (commandsRequiringEnvFile.includes(command) && !fs.existsSync(envFile)) {
|
|
32
39
|
console.error("No env file found.");
|
|
33
40
|
process.exit(1);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
43
|
+
if (args.strict && commandsRequiringEnvFile.includes(command)) {
|
|
44
|
+
const env = JSON.parse(fs.readFileSync(envFile, "utf8"));
|
|
45
|
+
const result = verifyCanDecrypt(env, secret);
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
console.error(`Strict mode: ${result.error}`);
|
|
48
|
+
process.exit(1);
|
|
41
49
|
}
|
|
50
|
+
}
|
|
42
51
|
|
|
52
|
+
function loadWorkspace() {
|
|
43
53
|
const target = resolveTarget(args.target);
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const rootPkgPath = path.resolve(__root, "package.json");
|
|
49
|
-
const rootPkgContent = fs.readFileSync(rootPkgPath, "utf8");
|
|
50
|
-
const rootPkg = JSON.parse(rootPkgContent);
|
|
51
|
-
|
|
54
|
+
const env = JSON.parse(fs.readFileSync(envFile, "utf8"));
|
|
55
|
+
const rootPkg = JSON.parse(
|
|
56
|
+
fs.readFileSync(path.resolve(__root, "package.json"), "utf8"),
|
|
57
|
+
);
|
|
52
58
|
const scope = rootPkg.name.split("/")[0];
|
|
53
59
|
const [_, ...foundPackages] = findMonorepoPackages(__root, scope);
|
|
54
|
-
|
|
55
60
|
const deps = [
|
|
56
61
|
...foundPackages.filter(Boolean).map((pkg) => ({
|
|
57
62
|
name: pkg.name.replace(`${scope}/`, ""),
|
|
58
63
|
dir: pkg.dir,
|
|
59
64
|
})),
|
|
60
|
-
{
|
|
61
|
-
name: "root",
|
|
62
|
-
dir: __root,
|
|
63
|
-
},
|
|
65
|
+
{ name: "root", dir: __root },
|
|
64
66
|
];
|
|
67
|
+
const depsName = deps.map((d) => d.name);
|
|
68
|
+
const allowedEnvs =
|
|
69
|
+
env?.envs && Array.isArray(env.envs) ? env.envs : ["dev"];
|
|
70
|
+
return { env, deps, depsName, allowedEnvs, target };
|
|
71
|
+
}
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
function detectCurrentPackage(deps) {
|
|
74
|
+
for (const dep of deps) {
|
|
75
|
+
if (dep.name === "root") continue;
|
|
76
|
+
const depDir = path.resolve(dep.dir);
|
|
77
|
+
if (__cwd === depDir || __cwd.startsWith(depDir + path.sep)) {
|
|
78
|
+
return dep.name;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const targetEnv = allowedEnvs.find((env) => env === target);
|
|
71
|
-
if (!targetEnv) {
|
|
84
|
+
function validateTarget(target, allowedEnvs) {
|
|
85
|
+
if (!allowedEnvs.includes(target)) {
|
|
72
86
|
console.error(`Target env "${target}" is not allowed.`);
|
|
73
87
|
process.exit(1);
|
|
74
88
|
}
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
75
91
|
|
|
76
|
-
|
|
92
|
+
function printJson(allEnvs, deps, isSinglePackage) {
|
|
93
|
+
if (isSinglePackage) {
|
|
94
|
+
console.log(JSON.stringify(allEnvs[deps[0].name] || {}, null, 2));
|
|
95
|
+
} else {
|
|
96
|
+
const out = {};
|
|
97
|
+
for (const dep of deps) out[dep.name] = allEnvs[dep.name] || {};
|
|
98
|
+
console.log(JSON.stringify(out, null, 2));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
77
101
|
|
|
78
|
-
|
|
102
|
+
function printKeyValue(allEnvs, deps, targetEnv) {
|
|
103
|
+
for (const dep of deps) {
|
|
79
104
|
const pkgEnv = Object.entries(allEnvs[dep.name] || {})
|
|
80
105
|
.map(([key, value]) => `${key}=${value}`)
|
|
81
106
|
.join("\n");
|
|
82
|
-
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
107
|
+
console.log("# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
83
108
|
console.log(
|
|
84
|
-
|
|
109
|
+
`# ===> ${dep.name} : in ${dep.dir} with target ${targetEnv}`,
|
|
85
110
|
);
|
|
111
|
+
console.log("# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
86
112
|
console.log(pkgEnv);
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
|
|
90
|
-
async function
|
|
116
|
+
async function showCommand() {
|
|
91
117
|
if (!secret) {
|
|
92
|
-
console.
|
|
118
|
+
console.error(
|
|
93
119
|
"Secret not provided. Use --secret flag or WRENV_SECRET env variable.",
|
|
94
120
|
);
|
|
95
121
|
}
|
|
96
122
|
|
|
97
|
-
const target =
|
|
123
|
+
const { env, deps, depsName, allowedEnvs, target } = loadWorkspace();
|
|
124
|
+
const targetEnv = validateTarget(target, allowedEnvs);
|
|
98
125
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
126
|
+
const selectedPkg = args.package || detectCurrentPackage(deps);
|
|
127
|
+
const filteredDeps = selectedPkg
|
|
128
|
+
? deps.filter((d) => d.name === selectedPkg)
|
|
129
|
+
: deps;
|
|
130
|
+
if (selectedPkg && filteredDeps.length === 0) {
|
|
131
|
+
console.error(`Package "${selectedPkg}" not found.`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
101
134
|
|
|
102
|
-
const
|
|
103
|
-
const rootPkgContent = fs.readFileSync(rootPkgPath, "utf8");
|
|
104
|
-
const rootPkg = JSON.parse(rootPkgContent);
|
|
105
|
-
const scope = rootPkg.name.split("/")[0];
|
|
135
|
+
const allEnvs = buildEnv(secret, targetEnv, env, depsName);
|
|
106
136
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
name: "root",
|
|
115
|
-
dir: __root,
|
|
116
|
-
},
|
|
117
|
-
];
|
|
118
|
-
const depsName = deps.map((dep) => dep.name);
|
|
137
|
+
if (args.format === "json") {
|
|
138
|
+
printJson(allEnvs, filteredDeps, !!selectedPkg);
|
|
139
|
+
} else {
|
|
140
|
+
printKeyValue(allEnvs, filteredDeps, targetEnv);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
119
143
|
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
async function writeCommand() {
|
|
145
|
+
if (!secret) {
|
|
146
|
+
console.log(
|
|
147
|
+
"Secret not provided. Use --secret flag or WRENV_SECRET env variable.",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { env, deps, depsName, allowedEnvs, target } = loadWorkspace();
|
|
122
152
|
|
|
123
153
|
// Handle 'all' target: write all environments with suffixed filenames
|
|
124
154
|
const isAllTarget = target === "all";
|
|
125
155
|
const targetEnvs = isAllTarget ? allowedEnvs : [target];
|
|
126
156
|
|
|
127
157
|
for (const targetEnv of targetEnvs) {
|
|
128
|
-
|
|
129
|
-
console.error(`Target env "${targetEnv}" is not allowed.`);
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
158
|
+
validateTarget(targetEnv, allowedEnvs);
|
|
132
159
|
|
|
133
160
|
const allEnvs = buildEnv(secret, targetEnv, env, depsName);
|
|
134
161
|
|
|
@@ -147,8 +174,7 @@ async function writeCommand() {
|
|
|
147
174
|
}
|
|
148
175
|
|
|
149
176
|
async function encryptCommand() {
|
|
150
|
-
const
|
|
151
|
-
const env = JSON.parse(envContent);
|
|
177
|
+
const env = JSON.parse(fs.readFileSync(envFile, "utf8"));
|
|
152
178
|
|
|
153
179
|
if (!secret) {
|
|
154
180
|
console.error(
|
|
@@ -172,31 +198,65 @@ async function encryptCommand() {
|
|
|
172
198
|
process.exit(1);
|
|
173
199
|
}
|
|
174
200
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
walkEnvValues(
|
|
202
|
+
env,
|
|
203
|
+
(v) => typeof v === "string" && !isEncrypted(v),
|
|
204
|
+
(v) => encrypt(v, secret),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await fs.writeFile(envFile, JSON.stringify(env, null, 2));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function decryptCommand() {
|
|
211
|
+
const env = JSON.parse(fs.readFileSync(envFile, "utf8"));
|
|
212
|
+
|
|
213
|
+
if (!secret) {
|
|
214
|
+
console.error(
|
|
215
|
+
"Cannot decrypt env file: secret not provided. Doing nothing",
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
183
218
|
}
|
|
184
219
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
220
|
+
const verification = verifyCanDecrypt(env, secret);
|
|
221
|
+
if (!verification.success) {
|
|
222
|
+
console.error(
|
|
223
|
+
`Cannot decrypt: the provided secret cannot decrypt encrypted value at "${verification.path}".`,
|
|
224
|
+
);
|
|
225
|
+
process.exit(1);
|
|
195
226
|
}
|
|
196
227
|
|
|
228
|
+
walkEnvValues(env, isEncrypted, (v) => decrypt(v, secret));
|
|
229
|
+
|
|
197
230
|
await fs.writeFile(envFile, JSON.stringify(env, null, 2));
|
|
198
231
|
}
|
|
199
232
|
|
|
233
|
+
async function getCommand() {
|
|
234
|
+
const varName = args._[1];
|
|
235
|
+
if (!varName) {
|
|
236
|
+
console.error("Variable name is required.");
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { env, deps, depsName, allowedEnvs, target } = loadWorkspace();
|
|
241
|
+
const targetEnv = validateTarget(target, allowedEnvs);
|
|
242
|
+
|
|
243
|
+
const selectedPkg = args.package || detectCurrentPackage(deps) || "root";
|
|
244
|
+
if (!depsName.includes(selectedPkg)) {
|
|
245
|
+
console.error(`Package "${selectedPkg}" not found.`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const allEnvs = buildEnv(secret, targetEnv, env, depsName);
|
|
250
|
+
const pkgEnv = allEnvs[selectedPkg] || {};
|
|
251
|
+
|
|
252
|
+
if (!Object.prototype.hasOwnProperty.call(pkgEnv, varName)) {
|
|
253
|
+
console.error(`Variable "${varName}" not found.`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
process.stdout.write(pkgEnv[varName]);
|
|
258
|
+
}
|
|
259
|
+
|
|
200
260
|
async function besafeCommand() {
|
|
201
261
|
// Find git root to ensure we're in a repository and use correct base path
|
|
202
262
|
const { stdout: gitRoot, exitCode: gitCheck } = await execa(
|
|
@@ -295,7 +355,8 @@ async function runCommand() {
|
|
|
295
355
|
targetEnv: args.target,
|
|
296
356
|
envPath: args.env,
|
|
297
357
|
applyToProcess: false,
|
|
298
|
-
expose
|
|
358
|
+
expose,
|
|
359
|
+
strict: !!args.strict
|
|
299
360
|
});
|
|
300
361
|
} catch (error) {
|
|
301
362
|
console.error(`wrenv: ${error.message}`);
|
|
@@ -322,7 +383,7 @@ async function runCommand() {
|
|
|
322
383
|
function helpCommand() {
|
|
323
384
|
console.log("Usage: wrenv [command] [options]\n");
|
|
324
385
|
console.log("Commands:");
|
|
325
|
-
console.log(" write Write .env files for all packages
|
|
386
|
+
console.log(" write Write .env files for all packages");
|
|
326
387
|
console.log(
|
|
327
388
|
" show Show environment variables without writing files",
|
|
328
389
|
);
|
|
@@ -332,10 +393,16 @@ function helpCommand() {
|
|
|
332
393
|
console.log(
|
|
333
394
|
" encrypt Encrypt all unencrypted variables in .env.json",
|
|
334
395
|
);
|
|
396
|
+
console.log(
|
|
397
|
+
" decrypt Decrypt all encrypted variables in .env.json",
|
|
398
|
+
);
|
|
335
399
|
console.log(" besafe Encrypt and stage .env.json if it has changes");
|
|
336
400
|
console.log(
|
|
337
401
|
" add <var> Add a new variable with environment-specific values",
|
|
338
402
|
);
|
|
403
|
+
console.log(
|
|
404
|
+
" get <var> Get the value of a specific variable",
|
|
405
|
+
);
|
|
339
406
|
console.log(" help Show this help message\n");
|
|
340
407
|
console.log("Options:");
|
|
341
408
|
console.log(
|
|
@@ -345,7 +412,16 @@ function helpCommand() {
|
|
|
345
412
|
" --target <env> Target environment (dev, staging, prod, all)",
|
|
346
413
|
);
|
|
347
414
|
console.log(
|
|
348
|
-
" --env <path> Path to env file (default: .env.json)
|
|
415
|
+
" --env <path> Path to env file (default: .env.json)",
|
|
416
|
+
);
|
|
417
|
+
console.log(
|
|
418
|
+
" --strict Error if encrypted values cannot be decrypted",
|
|
419
|
+
);
|
|
420
|
+
console.log(
|
|
421
|
+
" --format <fmt> Output format for show (json)",
|
|
422
|
+
);
|
|
423
|
+
console.log(
|
|
424
|
+
" --package <name> Show/get variables for a specific package\n",
|
|
349
425
|
);
|
|
350
426
|
console.log("Environment Variables:");
|
|
351
427
|
console.log(" WRENV_SECRET Secret for encryption/decryption");
|
|
@@ -356,6 +432,8 @@ function helpCommand() {
|
|
|
356
432
|
console.log(" wrenv --secret ~/.big-secret write --target dev");
|
|
357
433
|
console.log(" wrenv --secret stdin write < ~/.big-secret");
|
|
358
434
|
console.log(" wrenv show --target prod");
|
|
435
|
+
console.log(" wrenv show --format json --package root");
|
|
436
|
+
console.log(" wrenv get API_URL --target prod --package pkg-a");
|
|
359
437
|
console.log(" wrenv run --target prod -- node server.js");
|
|
360
438
|
console.log(" wrenv run -- npm test --coverage");
|
|
361
439
|
console.log(
|
|
@@ -371,7 +449,9 @@ const commands = {
|
|
|
371
449
|
show: showCommand,
|
|
372
450
|
besafe: besafeCommand,
|
|
373
451
|
encrypt: encryptCommand,
|
|
452
|
+
decrypt: decryptCommand,
|
|
374
453
|
add: addCommand,
|
|
454
|
+
get: getCommand,
|
|
375
455
|
run: runCommand,
|
|
376
456
|
help: helpCommand,
|
|
377
457
|
};
|
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -17,6 +17,7 @@ import { findMonorepoPackages } from "./utils-pkg.js";
|
|
|
17
17
|
* @property {string} [cwd] - Working directory (default: process.cwd())
|
|
18
18
|
* @property {boolean} [applyToProcess] - Whether to apply to process.env (default: true)
|
|
19
19
|
* @property {boolean} [expose] - wether to expose the secret or not
|
|
20
|
+
* @property {boolean} [strict] - Error if encrypted values cannot be decrypted
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -26,6 +27,7 @@ import { findMonorepoPackages } from "./utils-pkg.js";
|
|
|
26
27
|
* @property {string} [envPath] - Path to env.json file (default: '.env.json')
|
|
27
28
|
* @property {string} [cwd] - Working directory (default: process.cwd())
|
|
28
29
|
* @property {boolean} [expose] - wether to expose the secret or not
|
|
30
|
+
* @property {boolean} [strict] - Error if encrypted values cannot be decrypted
|
|
29
31
|
*/
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -263,7 +265,7 @@ export function loadLocalOverrides(cwd = process.cwd()) {
|
|
|
263
265
|
* @param {string} envPath - Name of the env file (e.g. '.env.json')
|
|
264
266
|
* @returns {string|null} - Path to monorepo root, or null if not found
|
|
265
267
|
*/
|
|
266
|
-
function findMonorepoRoot(cwd, envPath) {
|
|
268
|
+
export function findMonorepoRoot(cwd, envPath) {
|
|
267
269
|
let currentDir = cwd;
|
|
268
270
|
|
|
269
271
|
while (currentDir !== path.dirname(currentDir)) {
|
|
@@ -290,7 +292,8 @@ export function loadEnvJson(options = {}) {
|
|
|
290
292
|
secret: secretParam,
|
|
291
293
|
envPath = ".env.json",
|
|
292
294
|
cwd = process.cwd(),
|
|
293
|
-
expose = false
|
|
295
|
+
expose = false,
|
|
296
|
+
strict = false
|
|
294
297
|
} = options;
|
|
295
298
|
|
|
296
299
|
const secret = resolveSecret(secretParam);
|
|
@@ -306,6 +309,13 @@ export function loadEnvJson(options = {}) {
|
|
|
306
309
|
const envContent = fs.readFileSync(envFile, "utf8");
|
|
307
310
|
const env = JSON.parse(envContent);
|
|
308
311
|
|
|
312
|
+
if (strict) {
|
|
313
|
+
const result = verifyCanDecrypt(env, secret);
|
|
314
|
+
if (!result.success) {
|
|
315
|
+
throw new Error(`Strict mode: ${result.error}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
309
319
|
const allowedEnvs =
|
|
310
320
|
env && env.envs && Array.isArray(env.envs) ? env.envs : [DEFAULT_ENV];
|
|
311
321
|
const resolvedTarget = allowedEnvs.find((e) => e === targetEnv);
|
|
@@ -328,7 +338,7 @@ export function loadEnvJson(options = {}) {
|
|
|
328
338
|
const rootPkg = JSON.parse(rootPkgContent);
|
|
329
339
|
scope = rootPkg.name.split("/")[0];
|
|
330
340
|
|
|
331
|
-
const foundPackages = findMonorepoPackages(monorepoRoot, scope);
|
|
341
|
+
const [, ...foundPackages] = findMonorepoPackages(monorepoRoot, scope);
|
|
332
342
|
depsName = [
|
|
333
343
|
...foundPackages.map((pkg) => pkg.name.replace(`${scope}/`, "")),
|
|
334
344
|
"root",
|
|
@@ -378,7 +388,8 @@ export function load(options = {}) {
|
|
|
378
388
|
envPath,
|
|
379
389
|
cwd = process.cwd(),
|
|
380
390
|
applyToProcess = true,
|
|
381
|
-
expose = false
|
|
391
|
+
expose = false,
|
|
392
|
+
strict = false
|
|
382
393
|
} = options;
|
|
383
394
|
|
|
384
395
|
// Resolve target first to determine if we're in dev mode
|
|
@@ -390,7 +401,8 @@ export function load(options = {}) {
|
|
|
390
401
|
targetEnv: resolvedTarget,
|
|
391
402
|
envPath,
|
|
392
403
|
cwd,
|
|
393
|
-
expose
|
|
404
|
+
expose,
|
|
405
|
+
strict
|
|
394
406
|
});
|
|
395
407
|
|
|
396
408
|
// Load .env.local overrides only in dev mode (security: prevent local overrides in production)
|
|
@@ -446,10 +458,6 @@ export function verifyCanDecrypt(env, secret) {
|
|
|
446
458
|
/** @type {EncryptedValueInfo[]} */
|
|
447
459
|
const encryptedValues = [];
|
|
448
460
|
|
|
449
|
-
if (!secret) {
|
|
450
|
-
return { success: false, path: null, error: "No secret provided" };
|
|
451
|
-
}
|
|
452
|
-
|
|
453
461
|
// Collect all encrypted values from variables
|
|
454
462
|
if (env.variables) {
|
|
455
463
|
for (const [varName, varValue] of Object.entries(env.variables)) {
|
|
@@ -485,6 +493,10 @@ export function verifyCanDecrypt(env, secret) {
|
|
|
485
493
|
return { success: true };
|
|
486
494
|
}
|
|
487
495
|
|
|
496
|
+
if (!secret) {
|
|
497
|
+
return { success: false, path: null, error: "No secret provided" };
|
|
498
|
+
}
|
|
499
|
+
|
|
488
500
|
// Try to decrypt each encrypted value
|
|
489
501
|
for (const { path, value } of encryptedValues) {
|
|
490
502
|
try {
|
package/src/index.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface LoadOptions {
|
|
|
12
12
|
cwd?: string;
|
|
13
13
|
/** Whether to apply resolved vars to process.env (default: true) */
|
|
14
14
|
applyToProcess?: boolean;
|
|
15
|
+
/** Whether to expose WRENV_TARGET and WRENV_SECRET in the output (default: false) */
|
|
16
|
+
expose?: boolean;
|
|
17
|
+
/** Error if encrypted values cannot be decrypted (default: false) */
|
|
18
|
+
strict?: boolean;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -28,6 +32,10 @@ export function loadEnvJson(options?: {
|
|
|
28
32
|
targetEnv?: string;
|
|
29
33
|
envPath?: string;
|
|
30
34
|
cwd?: string;
|
|
35
|
+
/** Whether to expose WRENV_TARGET and WRENV_SECRET in the output (default: false) */
|
|
36
|
+
expose?: boolean;
|
|
37
|
+
/** Error if encrypted values cannot be decrypted (default: false) */
|
|
38
|
+
strict?: boolean;
|
|
31
39
|
}): EnvRecord;
|
|
32
40
|
|
|
33
41
|
/** Load .env.local overrides if the file exists */
|
package/src/utils-env.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { decrypt, isEncrypted } from "./utils-crypto.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Walk all string values in env.variables and env.overrides,
|
|
5
|
+
* applying transform when predicate matches. Mutates env in place.
|
|
6
|
+
* @param {EnvConfig} env
|
|
7
|
+
* @param {(value: string) => boolean} predicate
|
|
8
|
+
* @param {(value: string) => string} transform
|
|
9
|
+
*/
|
|
10
|
+
export function walkEnvValues(env, predicate, transform) {
|
|
11
|
+
for (const varValue of Object.values(env.variables || {}))
|
|
12
|
+
for (const [envKey, envVal] of Object.entries(varValue))
|
|
13
|
+
if (predicate(envVal)) varValue[envKey] = transform(envVal);
|
|
14
|
+
|
|
15
|
+
for (const pkgOverrides of Object.values(env.overrides || {}))
|
|
16
|
+
for (const varValue of Object.values(pkgOverrides))
|
|
17
|
+
for (const [envKey, envVal] of Object.entries(varValue))
|
|
18
|
+
if (predicate(envVal)) varValue[envKey] = transform(envVal);
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
/**
|
|
4
22
|
* Environment-specific values for a single variable.
|
|
5
23
|
* Keys are environment names (e.g. 'dev', 'production') or '@@' for the fallback.
|