@saasak/tool-env 1.2.2 → 1.4.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 +23 -4
- package/package.json +1 -1
- package/src/core.js +33 -8
- package/src/index.d.ts +8 -0
package/bin/index.js
CHANGED
|
@@ -16,10 +16,14 @@ import {
|
|
|
16
16
|
load,
|
|
17
17
|
} from "../src/core.js";
|
|
18
18
|
|
|
19
|
-
const args = minimist(process.argv.slice(2), {
|
|
19
|
+
const args = minimist(process.argv.slice(2), {
|
|
20
|
+
"--": true,
|
|
21
|
+
boolean: ["strict", "expose", "help", "h"],
|
|
22
|
+
});
|
|
20
23
|
const __root = process.cwd();
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
let command = args._[0] || "help";
|
|
26
|
+
if (args.help || args.h) command = "help";
|
|
23
27
|
|
|
24
28
|
const secret = resolveSecret(args.secret ? `file://${args.secret}` : '');
|
|
25
29
|
|
|
@@ -33,6 +37,15 @@ if (commandsRequiringEnvFile.includes(command) && !fs.existsSync(envFile)) {
|
|
|
33
37
|
process.exit(1);
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
if (args.strict && commandsRequiringEnvFile.includes(command)) {
|
|
41
|
+
const env = JSON.parse(fs.readFileSync(envFile, "utf8"));
|
|
42
|
+
const result = verifyCanDecrypt(env, secret);
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
console.error(`Strict mode: ${result.error}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
async function showCommand() {
|
|
37
50
|
if (!secret) {
|
|
38
51
|
console.log(
|
|
@@ -280,6 +293,7 @@ async function addCommand() {
|
|
|
280
293
|
async function runCommand() {
|
|
281
294
|
const commandParts = args["--"] || [];
|
|
282
295
|
const [cmd, ...cmdArgs] = commandParts;
|
|
296
|
+
const expose = !!args.expose
|
|
283
297
|
|
|
284
298
|
if (!cmd) {
|
|
285
299
|
console.error("Usage: wrenv run [options] -- <command> [args...]");
|
|
@@ -294,6 +308,8 @@ async function runCommand() {
|
|
|
294
308
|
targetEnv: args.target,
|
|
295
309
|
envPath: args.env,
|
|
296
310
|
applyToProcess: false,
|
|
311
|
+
expose,
|
|
312
|
+
strict: !!args.strict
|
|
297
313
|
});
|
|
298
314
|
} catch (error) {
|
|
299
315
|
console.error(`wrenv: ${error.message}`);
|
|
@@ -320,7 +336,7 @@ async function runCommand() {
|
|
|
320
336
|
function helpCommand() {
|
|
321
337
|
console.log("Usage: wrenv [command] [options]\n");
|
|
322
338
|
console.log("Commands:");
|
|
323
|
-
console.log(" write Write .env files for all packages
|
|
339
|
+
console.log(" write Write .env files for all packages");
|
|
324
340
|
console.log(
|
|
325
341
|
" show Show environment variables without writing files",
|
|
326
342
|
);
|
|
@@ -343,7 +359,10 @@ function helpCommand() {
|
|
|
343
359
|
" --target <env> Target environment (dev, staging, prod, all)",
|
|
344
360
|
);
|
|
345
361
|
console.log(
|
|
346
|
-
" --env <path> Path to env file (default: .env.json)
|
|
362
|
+
" --env <path> Path to env file (default: .env.json)",
|
|
363
|
+
);
|
|
364
|
+
console.log(
|
|
365
|
+
" --strict Error if encrypted values cannot be decrypted\n",
|
|
347
366
|
);
|
|
348
367
|
console.log("Environment Variables:");
|
|
349
368
|
console.log(" WRENV_SECRET Secret for encryption/decryption");
|
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -16,6 +16,8 @@ import { findMonorepoPackages } from "./utils-pkg.js";
|
|
|
16
16
|
* @property {string} [envPath] - Path to env.json file (default: '.env.json')
|
|
17
17
|
* @property {string} [cwd] - Working directory (default: process.cwd())
|
|
18
18
|
* @property {boolean} [applyToProcess] - Whether to apply to process.env (default: true)
|
|
19
|
+
* @property {boolean} [expose] - wether to expose the secret or not
|
|
20
|
+
* @property {boolean} [strict] - Error if encrypted values cannot be decrypted
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -24,6 +26,8 @@ import { findMonorepoPackages } from "./utils-pkg.js";
|
|
|
24
26
|
* @property {string} [targetEnv] - Target environment
|
|
25
27
|
* @property {string} [envPath] - Path to env.json file (default: '.env.json')
|
|
26
28
|
* @property {string} [cwd] - Working directory (default: process.cwd())
|
|
29
|
+
* @property {boolean} [expose] - wether to expose the secret or not
|
|
30
|
+
* @property {boolean} [strict] - Error if encrypted values cannot be decrypted
|
|
27
31
|
*/
|
|
28
32
|
|
|
29
33
|
/**
|
|
@@ -288,6 +292,8 @@ export function loadEnvJson(options = {}) {
|
|
|
288
292
|
secret: secretParam,
|
|
289
293
|
envPath = ".env.json",
|
|
290
294
|
cwd = process.cwd(),
|
|
295
|
+
expose = false,
|
|
296
|
+
strict = false
|
|
291
297
|
} = options;
|
|
292
298
|
|
|
293
299
|
const secret = resolveSecret(secretParam);
|
|
@@ -303,6 +309,13 @@ export function loadEnvJson(options = {}) {
|
|
|
303
309
|
const envContent = fs.readFileSync(envFile, "utf8");
|
|
304
310
|
const env = JSON.parse(envContent);
|
|
305
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
|
+
|
|
306
319
|
const allowedEnvs =
|
|
307
320
|
env && env.envs && Array.isArray(env.envs) ? env.envs : [DEFAULT_ENV];
|
|
308
321
|
const resolvedTarget = allowedEnvs.find((e) => e === targetEnv);
|
|
@@ -341,7 +354,13 @@ export function loadEnvJson(options = {}) {
|
|
|
341
354
|
const currentPackageName = findNearestPackageName(cwd, scope);
|
|
342
355
|
|
|
343
356
|
// Return env for current package (with fallback to root)
|
|
344
|
-
|
|
357
|
+
const finalEnv = allEnvs[currentPackageName] || allEnvs.root || {};
|
|
358
|
+
|
|
359
|
+
return expose ? {
|
|
360
|
+
...finalEnv,
|
|
361
|
+
WRENV_TARGET: resolvedTarget,
|
|
362
|
+
WRENV_SECRET: secret
|
|
363
|
+
} : finalEnv;
|
|
345
364
|
}
|
|
346
365
|
|
|
347
366
|
/**
|
|
@@ -369,6 +388,8 @@ export function load(options = {}) {
|
|
|
369
388
|
envPath,
|
|
370
389
|
cwd = process.cwd(),
|
|
371
390
|
applyToProcess = true,
|
|
391
|
+
expose = false,
|
|
392
|
+
strict = false
|
|
372
393
|
} = options;
|
|
373
394
|
|
|
374
395
|
// Resolve target first to determine if we're in dev mode
|
|
@@ -380,6 +401,8 @@ export function load(options = {}) {
|
|
|
380
401
|
targetEnv: resolvedTarget,
|
|
381
402
|
envPath,
|
|
382
403
|
cwd,
|
|
404
|
+
expose,
|
|
405
|
+
strict
|
|
383
406
|
});
|
|
384
407
|
|
|
385
408
|
// Load .env.local overrides only in dev mode (security: prevent local overrides in production)
|
|
@@ -398,10 +421,12 @@ export function load(options = {}) {
|
|
|
398
421
|
}
|
|
399
422
|
|
|
400
423
|
// - Reserved vars (WRENV_*, TARGET_*) are never injected
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
424
|
+
if (!expose) {
|
|
425
|
+
for (const key of RESERVED_ENV_VARS) {
|
|
426
|
+
delete finalConfig[key];
|
|
427
|
+
}
|
|
404
428
|
}
|
|
429
|
+
// - NODE_ENV is only injected if not already set
|
|
405
430
|
if (process.env.NODE_ENV) {
|
|
406
431
|
delete finalConfig.NODE_ENV;
|
|
407
432
|
}
|
|
@@ -433,10 +458,6 @@ export function verifyCanDecrypt(env, secret) {
|
|
|
433
458
|
/** @type {EncryptedValueInfo[]} */
|
|
434
459
|
const encryptedValues = [];
|
|
435
460
|
|
|
436
|
-
if (!secret) {
|
|
437
|
-
return { success: false, path: null, error: "No secret provided" };
|
|
438
|
-
}
|
|
439
|
-
|
|
440
461
|
// Collect all encrypted values from variables
|
|
441
462
|
if (env.variables) {
|
|
442
463
|
for (const [varName, varValue] of Object.entries(env.variables)) {
|
|
@@ -472,6 +493,10 @@ export function verifyCanDecrypt(env, secret) {
|
|
|
472
493
|
return { success: true };
|
|
473
494
|
}
|
|
474
495
|
|
|
496
|
+
if (!secret) {
|
|
497
|
+
return { success: false, path: null, error: "No secret provided" };
|
|
498
|
+
}
|
|
499
|
+
|
|
475
500
|
// Try to decrypt each encrypted value
|
|
476
501
|
for (const { path, value } of encryptedValues) {
|
|
477
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 */
|