@saasak/tool-env 1.0.2 → 1.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.
- package/README.md +25 -4
- package/bin/index.js +218 -103
- package/package.json +10 -2
- package/src/core.js +491 -0
- package/src/index.d.ts +82 -0
- package/src/index.js +42 -0
- package/src/utils-crypto.js +25 -6
- package/src/utils-env.js +74 -17
- package/src/utils-pkg.js +72 -55
package/README.md
CHANGED
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
## TODOs
|
|
16
16
|
|
|
17
|
-
[] Handle encryption
|
|
18
|
-
[]
|
|
19
|
-
[]
|
|
20
|
-
[]
|
|
17
|
+
- [x] Handle encryption
|
|
18
|
+
- [ ] Fix not scoped package detection (if one package is not scope, it fails with pkg.name something)
|
|
19
|
+
- [ ] Handle variable composition everywhere (not just in overrides)
|
|
20
|
+
- [x] Create runtime library to read vars (even encrypted)
|
|
21
|
+
- [x] Handle env.local files
|
|
21
22
|
|
|
22
23
|
## Open questions
|
|
23
24
|
|
|
@@ -46,6 +47,26 @@ or
|
|
|
46
47
|
WRENV_TARGET=staging bun run env:update
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
### Writing all environments
|
|
51
|
+
|
|
52
|
+
Use `--target all` to write environment files for all configured environments at once.
|
|
53
|
+
This creates suffixed files using conventional names (`.env.development`, `.env.staging`, `.env.production`) instead of a single `.env` file.
|
|
54
|
+
|
|
55
|
+
Internal names are mapped to conventional output names:
|
|
56
|
+
- `dev` → `.env.development`
|
|
57
|
+
- `preprod` → `.env.staging`
|
|
58
|
+
- `production` → `.env.production`
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
wrenv --secret ~/.big-secret write --target all
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This is useful for:
|
|
65
|
+
- CI/CD pipelines that need all environment configurations
|
|
66
|
+
- Docker builds that copy environment-specific files
|
|
67
|
+
- Pre-generating all env files for deployment
|
|
68
|
+
- Compatibility with Next.js, Vite, and other frameworks that use conventional env file names
|
|
69
|
+
|
|
49
70
|
you can also pass the secret via an env variable (Even though it is not really encouraged)
|
|
50
71
|
```bash
|
|
51
72
|
WRENV_SECRET=super-secret WRENV_TARGET=prod npm run env:update
|
package/bin/index.js
CHANGED
|
@@ -1,71 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import minimist from
|
|
4
|
-
import fs from
|
|
5
|
-
import path from
|
|
6
|
-
import { execa } from
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import minimist from "minimist";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { execa } from "execa";
|
|
7
|
+
|
|
8
|
+
import { encrypt, isEncrypted } from "../src/utils-crypto.js";
|
|
9
|
+
import { buildEnv } from "../src/utils-env.js";
|
|
10
|
+
import { findMonorepoPackages } from "../src/utils-pkg.js";
|
|
11
|
+
import {
|
|
12
|
+
resolveTarget,
|
|
13
|
+
resolveSecret,
|
|
14
|
+
verifyCanDecrypt,
|
|
15
|
+
getOutputName,
|
|
16
|
+
load,
|
|
17
|
+
} from "../src/core.js";
|
|
18
|
+
|
|
19
|
+
const args = minimist(process.argv.slice(2), { "--": true });
|
|
13
20
|
const __root = process.cwd();
|
|
14
21
|
|
|
15
|
-
const command = args._[0] ||
|
|
22
|
+
const command = args._[0] || "write";
|
|
16
23
|
|
|
17
|
-
const secret = args.secret
|
|
18
|
-
? (args.secret === 'stdin' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(args.secret, 'utf8'))
|
|
19
|
-
: process.env.WRENV_SECRET || process.env.TARGET_SECRET || '';
|
|
24
|
+
const secret = resolveSecret(args.secret);
|
|
20
25
|
|
|
21
|
-
const envPath = args.env ||
|
|
26
|
+
const envPath = args.env || ".env.json";
|
|
22
27
|
const envFile = path.resolve(__root, envPath);
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
// Check env file exists for commands that need it
|
|
30
|
+
const commandsRequiringEnvFile = ["write", "show", "encrypt", "besafe", "add"];
|
|
31
|
+
if (commandsRequiringEnvFile.includes(command) && !fs.existsSync(envFile)) {
|
|
32
|
+
console.error("No env file found.");
|
|
26
33
|
process.exit(1);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
const DEFAULT_ENV = 'dev';
|
|
30
|
-
const targets = {
|
|
31
|
-
"development": "dev",
|
|
32
|
-
"dev": "dev",
|
|
33
|
-
"staging": "preprod",
|
|
34
|
-
"pp": "preprod",
|
|
35
|
-
"preprod": "preprod",
|
|
36
|
-
"prod": "production",
|
|
37
|
-
"production": "production",
|
|
38
|
-
}
|
|
39
|
-
|
|
40
36
|
async function showCommand() {
|
|
41
37
|
if (!secret) {
|
|
42
|
-
console.log(
|
|
38
|
+
console.log(
|
|
39
|
+
"Secret not provided. Use --secret flag or WRENV_SECRET env variable.",
|
|
40
|
+
);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
const target =
|
|
43
|
+
const target = resolveTarget(args.target);
|
|
46
44
|
|
|
47
|
-
const envContent = fs.readFileSync(envFile,
|
|
45
|
+
const envContent = fs.readFileSync(envFile, "utf8");
|
|
48
46
|
const env = JSON.parse(envContent);
|
|
49
47
|
|
|
50
|
-
const rootPkgPath = path.resolve(__root,
|
|
51
|
-
const rootPkgContent = fs.readFileSync(rootPkgPath,
|
|
48
|
+
const rootPkgPath = path.resolve(__root, "package.json");
|
|
49
|
+
const rootPkgContent = fs.readFileSync(rootPkgPath, "utf8");
|
|
52
50
|
const rootPkg = JSON.parse(rootPkgContent);
|
|
53
|
-
const scope = rootPkg.name.split('/')[0];
|
|
54
51
|
|
|
55
|
-
const
|
|
52
|
+
const scope = rootPkg.name.split("/")[0];
|
|
53
|
+
const [_, ...foundPackages] = findMonorepoPackages(__root, scope);
|
|
54
|
+
|
|
56
55
|
const deps = [
|
|
57
|
-
...foundPackages.map((pkg) => ({
|
|
58
|
-
name: pkg.name.replace(`${scope}/`,
|
|
56
|
+
...foundPackages.filter(Boolean).map((pkg) => ({
|
|
57
|
+
name: pkg.name.replace(`${scope}/`, ""),
|
|
59
58
|
dir: pkg.dir,
|
|
60
59
|
})),
|
|
61
60
|
{
|
|
62
|
-
name:
|
|
61
|
+
name: "root",
|
|
63
62
|
dir: __root,
|
|
64
63
|
},
|
|
65
64
|
];
|
|
65
|
+
|
|
66
66
|
const depsName = deps.map((dep) => dep.name);
|
|
67
67
|
|
|
68
|
-
const allowedEnvs =
|
|
68
|
+
const allowedEnvs =
|
|
69
|
+
env && env.envs && Array.isArray(env.envs) ? env.envs : ["dev"];
|
|
69
70
|
const targetEnv = allowedEnvs.find((env) => env === target);
|
|
70
71
|
if (!targetEnv) {
|
|
71
72
|
console.error(`Target env "${target}" is not allowed.`);
|
|
@@ -77,73 +78,104 @@ async function showCommand() {
|
|
|
77
78
|
for await (const dep of deps) {
|
|
78
79
|
const pkgEnv = Object.entries(allEnvs[dep.name] || {})
|
|
79
80
|
.map(([key, value]) => `${key}=${value}`)
|
|
80
|
-
.join(
|
|
81
|
-
console.log(
|
|
82
|
-
console.log(
|
|
81
|
+
.join("\n");
|
|
82
|
+
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
|
83
|
+
console.log(
|
|
84
|
+
`=== ENV for ${dep.name} in ${dep.dir} with target ${targetEnv}`,
|
|
85
|
+
);
|
|
83
86
|
console.log(pkgEnv);
|
|
84
87
|
}
|
|
85
|
-
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
async function writeCommand() {
|
|
89
91
|
if (!secret) {
|
|
90
|
-
console.log(
|
|
92
|
+
console.log(
|
|
93
|
+
"Secret not provided. Use --secret flag or WRENV_SECRET env variable.",
|
|
94
|
+
);
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
const target =
|
|
97
|
+
const target = resolveTarget(args.target);
|
|
94
98
|
|
|
95
|
-
const envContent = fs.readFileSync(envFile,
|
|
99
|
+
const envContent = fs.readFileSync(envFile, "utf8");
|
|
96
100
|
const env = JSON.parse(envContent);
|
|
97
101
|
|
|
98
|
-
const rootPkgPath = path.resolve(__root,
|
|
99
|
-
const rootPkgContent = fs.readFileSync(rootPkgPath,
|
|
102
|
+
const rootPkgPath = path.resolve(__root, "package.json");
|
|
103
|
+
const rootPkgContent = fs.readFileSync(rootPkgPath, "utf8");
|
|
100
104
|
const rootPkg = JSON.parse(rootPkgContent);
|
|
101
|
-
const scope = rootPkg.name.split(
|
|
105
|
+
const scope = rootPkg.name.split("/")[0];
|
|
102
106
|
|
|
103
107
|
const [_, ...foundPackages] = await findMonorepoPackages(__root, scope);
|
|
104
108
|
const deps = [
|
|
105
109
|
...foundPackages.map((pkg) => ({
|
|
106
|
-
name: pkg.name.replace(`${scope}/`,
|
|
110
|
+
name: pkg.name.replace(`${scope}/`, ""),
|
|
107
111
|
dir: pkg.dir,
|
|
108
112
|
})),
|
|
109
113
|
{
|
|
110
|
-
name:
|
|
114
|
+
name: "root",
|
|
111
115
|
dir: __root,
|
|
112
116
|
},
|
|
113
117
|
];
|
|
114
118
|
const depsName = deps.map((dep) => dep.name);
|
|
115
119
|
|
|
116
|
-
const allowedEnvs =
|
|
117
|
-
|
|
118
|
-
if (!targetEnv) {
|
|
119
|
-
console.error(`Target env "${target}" is not allowed.`);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
120
|
+
const allowedEnvs =
|
|
121
|
+
env && env.envs && Array.isArray(env.envs) ? env.envs : ["dev"];
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
// Handle 'all' target: write all environments with suffixed filenames
|
|
124
|
+
const isAllTarget = target === "all";
|
|
125
|
+
const targetEnvs = isAllTarget ? allowedEnvs : [target];
|
|
124
126
|
|
|
125
|
-
for
|
|
126
|
-
|
|
127
|
-
.
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
for (const targetEnv of targetEnvs) {
|
|
128
|
+
if (!allowedEnvs.includes(targetEnv)) {
|
|
129
|
+
console.error(`Target env "${targetEnv}" is not allowed.`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const allEnvs = buildEnv(secret, targetEnv, env, depsName);
|
|
134
|
+
|
|
135
|
+
for await (const dep of deps) {
|
|
136
|
+
const pkgEnv = Object.entries(allEnvs[dep.name] || {})
|
|
137
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
138
|
+
.join("\n");
|
|
139
|
+
// Use suffixed filename (.env.{outputName}) for 'all', plain .env otherwise
|
|
140
|
+
const filename = isAllTarget
|
|
141
|
+
? `.env.${getOutputName(targetEnv)}`
|
|
142
|
+
: ".env";
|
|
143
|
+
console.log(`Writing ${filename} for ${dep.name} in ${dep.dir}`);
|
|
144
|
+
await fs.writeFile(path.join(dep.dir, filename), pkgEnv);
|
|
145
|
+
}
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
async function encryptCommand() {
|
|
135
|
-
const envContent = fs.readFileSync(envFile,
|
|
150
|
+
const envContent = fs.readFileSync(envFile, "utf8");
|
|
136
151
|
const env = JSON.parse(envContent);
|
|
137
152
|
|
|
138
153
|
if (!secret) {
|
|
139
|
-
console.error(
|
|
154
|
+
console.error(
|
|
155
|
+
"Cannot encrypt env file: secret not provided. Doing nothing",
|
|
156
|
+
);
|
|
140
157
|
return;
|
|
141
158
|
}
|
|
142
159
|
|
|
160
|
+
// Verify we can decrypt all existing encrypted values before encrypting new ones
|
|
161
|
+
const verification = verifyCanDecrypt(env, secret);
|
|
162
|
+
if (!verification.success) {
|
|
163
|
+
console.error(
|
|
164
|
+
`Cannot encrypt: the provided secret cannot decrypt existing encrypted value at "${verification.path}".`,
|
|
165
|
+
);
|
|
166
|
+
console.error(
|
|
167
|
+
"This would result in a file with mixed encryption passwords.",
|
|
168
|
+
);
|
|
169
|
+
console.error(
|
|
170
|
+
"Please provide the correct secret that was used for existing encrypted values.",
|
|
171
|
+
);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
143
175
|
if (env.variables) {
|
|
144
176
|
for (const [varName, varValue] of Object.entries(env.variables)) {
|
|
145
177
|
for (const [envKey, envVal] of Object.entries(varValue)) {
|
|
146
|
-
if (typeof envVal ===
|
|
178
|
+
if (typeof envVal === "string" && !isEncrypted(envVal)) {
|
|
147
179
|
env.variables[varName][envKey] = encrypt(envVal, secret);
|
|
148
180
|
}
|
|
149
181
|
}
|
|
@@ -154,7 +186,7 @@ async function encryptCommand() {
|
|
|
154
186
|
for (const [pkgName, pkgOverrides] of Object.entries(env.overrides)) {
|
|
155
187
|
for (const [varName, varValue] of Object.entries(pkgOverrides)) {
|
|
156
188
|
for (const [envKey, envVal] of Object.entries(varValue)) {
|
|
157
|
-
if (typeof envVal ===
|
|
189
|
+
if (typeof envVal === "string" && !isEncrypted(envVal)) {
|
|
158
190
|
env.overrides[pkgName][varName][envKey] = encrypt(envVal, secret);
|
|
159
191
|
}
|
|
160
192
|
}
|
|
@@ -167,7 +199,11 @@ async function encryptCommand() {
|
|
|
167
199
|
|
|
168
200
|
async function besafeCommand() {
|
|
169
201
|
// Find git root to ensure we're in a repository and use correct base path
|
|
170
|
-
const { stdout: gitRoot, exitCode: gitCheck } = await execa(
|
|
202
|
+
const { stdout: gitRoot, exitCode: gitCheck } = await execa(
|
|
203
|
+
"git",
|
|
204
|
+
["rev-parse", "--show-toplevel"],
|
|
205
|
+
{ cwd: __root, reject: false },
|
|
206
|
+
);
|
|
171
207
|
if (gitCheck !== 0) {
|
|
172
208
|
return;
|
|
173
209
|
}
|
|
@@ -175,9 +211,21 @@ async function besafeCommand() {
|
|
|
175
211
|
const relativeEnvPath = path.relative(gitRoot, envFile);
|
|
176
212
|
|
|
177
213
|
// Check if file has any changes (staged, unstaged, or untracked)
|
|
178
|
-
const { stdout: staged } = await execa(
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
const { stdout: staged } = await execa(
|
|
215
|
+
"git",
|
|
216
|
+
["diff", "--cached", "--name-only", "--", relativeEnvPath],
|
|
217
|
+
{ cwd: gitRoot, reject: false },
|
|
218
|
+
);
|
|
219
|
+
const { stdout: unstaged } = await execa(
|
|
220
|
+
"git",
|
|
221
|
+
["diff", "--name-only", "--", relativeEnvPath],
|
|
222
|
+
{ cwd: gitRoot, reject: false },
|
|
223
|
+
);
|
|
224
|
+
const { stdout: untracked } = await execa(
|
|
225
|
+
"git",
|
|
226
|
+
["ls-files", "--others", "--exclude-standard", "--", relativeEnvPath],
|
|
227
|
+
{ cwd: gitRoot, reject: false },
|
|
228
|
+
);
|
|
181
229
|
|
|
182
230
|
if (!staged?.trim() && !unstaged?.trim() && !untracked?.trim()) {
|
|
183
231
|
return;
|
|
@@ -186,24 +234,28 @@ async function besafeCommand() {
|
|
|
186
234
|
await encryptCommand();
|
|
187
235
|
|
|
188
236
|
if (!secret) {
|
|
189
|
-
console.error(
|
|
237
|
+
console.error(
|
|
238
|
+
"Warning: potential security risk: secret not provided. Variables WERE NOT encrypted.",
|
|
239
|
+
);
|
|
190
240
|
}
|
|
191
241
|
|
|
192
|
-
await execa(
|
|
242
|
+
await execa("git", ["add", relativeEnvPath], { cwd: gitRoot });
|
|
193
243
|
}
|
|
194
244
|
|
|
195
245
|
async function addCommand() {
|
|
196
246
|
const varName = args._[1];
|
|
197
247
|
if (!varName) {
|
|
198
|
-
console.error(
|
|
248
|
+
console.error("Variable name is required.");
|
|
199
249
|
process.exit(1);
|
|
200
250
|
}
|
|
201
251
|
|
|
202
252
|
if (!secret) {
|
|
203
|
-
console.error(
|
|
253
|
+
console.error(
|
|
254
|
+
"Warning: potential security risk: secret not provided. Variable WILL NOT be encrypted.",
|
|
255
|
+
);
|
|
204
256
|
}
|
|
205
257
|
|
|
206
|
-
const envContent = fs.readFileSync(envFile,
|
|
258
|
+
const envContent = fs.readFileSync(envFile, "utf8");
|
|
207
259
|
const env = JSON.parse(envContent);
|
|
208
260
|
|
|
209
261
|
if (!env.variables) {
|
|
@@ -215,39 +267,101 @@ async function addCommand() {
|
|
|
215
267
|
}
|
|
216
268
|
|
|
217
269
|
for (const arg of args._.slice(2)) {
|
|
218
|
-
if (!arg.startsWith(
|
|
219
|
-
const [key, ...valueParts] = arg.slice(1).split(
|
|
220
|
-
const value = valueParts.join(
|
|
221
|
-
const envKey = key ===
|
|
270
|
+
if (!arg.startsWith("+")) continue;
|
|
271
|
+
const [key, ...valueParts] = arg.slice(1).split("=");
|
|
272
|
+
const value = valueParts.join("=");
|
|
273
|
+
const envKey = key === "fallback" ? "@@" : key;
|
|
222
274
|
env.variables[varName][envKey] = secret ? encrypt(value, secret) : value;
|
|
223
275
|
}
|
|
224
276
|
|
|
225
277
|
await fs.writeFile(envFile, JSON.stringify(env, null, 2));
|
|
226
278
|
}
|
|
227
279
|
|
|
280
|
+
async function runCommand() {
|
|
281
|
+
const commandParts = args["--"] || [];
|
|
282
|
+
const [cmd, ...cmdArgs] = commandParts;
|
|
283
|
+
|
|
284
|
+
if (!cmd) {
|
|
285
|
+
console.error("Usage: wrenv run [options] -- <command> [args...]");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Load environment variables (without applying to current process.env)
|
|
290
|
+
let envVars = {};
|
|
291
|
+
try {
|
|
292
|
+
envVars = load({
|
|
293
|
+
secret: resolveSecret(args.secret),
|
|
294
|
+
targetEnv: args.target,
|
|
295
|
+
envPath: args.env,
|
|
296
|
+
applyToProcess: false,
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error(`wrenv: ${error.message}`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Use execa for cross-platform compatibility with minimal overhead
|
|
304
|
+
const subprocess = execa(cmd, cmdArgs, {
|
|
305
|
+
stdio: "inherit",
|
|
306
|
+
env: { ...process.env, ...envVars },
|
|
307
|
+
reject: false,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Forward signals to child process
|
|
311
|
+
const forwardSignal = (signal) => subprocess.kill(signal);
|
|
312
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
313
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
314
|
+
process.on("SIGHUP", () => forwardSignal("SIGHUP"));
|
|
315
|
+
|
|
316
|
+
const result = await subprocess;
|
|
317
|
+
process.exit(result.exitCode ?? 0);
|
|
318
|
+
}
|
|
319
|
+
|
|
228
320
|
function helpCommand() {
|
|
229
|
-
console.log(
|
|
230
|
-
console.log(
|
|
231
|
-
console.log(
|
|
232
|
-
console.log(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.log(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log(
|
|
242
|
-
console.log(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.log(
|
|
246
|
-
console.log(
|
|
247
|
-
console.log(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
console.log(
|
|
321
|
+
console.log("Usage: wrenv [command] [options]\n");
|
|
322
|
+
console.log("Commands:");
|
|
323
|
+
console.log(" write Write .env files for all packages (default)");
|
|
324
|
+
console.log(
|
|
325
|
+
" show Show environment variables without writing files",
|
|
326
|
+
);
|
|
327
|
+
console.log(
|
|
328
|
+
" run Run a command with injected environment variables",
|
|
329
|
+
);
|
|
330
|
+
console.log(
|
|
331
|
+
" encrypt Encrypt all unencrypted variables in .env.json",
|
|
332
|
+
);
|
|
333
|
+
console.log(" besafe Encrypt and stage .env.json if it has changes");
|
|
334
|
+
console.log(
|
|
335
|
+
" add <var> Add a new variable with environment-specific values",
|
|
336
|
+
);
|
|
337
|
+
console.log(" help Show this help message\n");
|
|
338
|
+
console.log("Options:");
|
|
339
|
+
console.log(
|
|
340
|
+
' --secret <path|stdin> Path to secret file or "stdin" to read from stdin',
|
|
341
|
+
);
|
|
342
|
+
console.log(
|
|
343
|
+
" --target <env> Target environment (dev, staging, prod, all)",
|
|
344
|
+
);
|
|
345
|
+
console.log(
|
|
346
|
+
" --env <path> Path to env file (default: .env.json)\n",
|
|
347
|
+
);
|
|
348
|
+
console.log("Environment Variables:");
|
|
349
|
+
console.log(" WRENV_SECRET Secret for encryption/decryption");
|
|
350
|
+
console.log(" WRENV_TARGET Target environment");
|
|
351
|
+
console.log(" TARGET_SECRET Alias for WRENV_SECRET");
|
|
352
|
+
console.log(" TARGET_ENV Alias for WRENV_TARGET\n");
|
|
353
|
+
console.log("Examples:");
|
|
354
|
+
console.log(" wrenv --secret ~/.big-secret write --target dev");
|
|
355
|
+
console.log(" wrenv --secret stdin write < ~/.big-secret");
|
|
356
|
+
console.log(" wrenv show --target prod");
|
|
357
|
+
console.log(" wrenv run --target prod -- node server.js");
|
|
358
|
+
console.log(" wrenv run -- npm test --coverage");
|
|
359
|
+
console.log(
|
|
360
|
+
" wrenv add NEW_VAR +fallback=value +dev=dev_value +production=prod_value",
|
|
361
|
+
);
|
|
362
|
+
console.log(
|
|
363
|
+
" wrenv write --target all # writes .env.development, .env.staging, .env.production",
|
|
364
|
+
);
|
|
251
365
|
}
|
|
252
366
|
|
|
253
367
|
const commands = {
|
|
@@ -256,6 +370,7 @@ const commands = {
|
|
|
256
370
|
besafe: besafeCommand,
|
|
257
371
|
encrypt: encryptCommand,
|
|
258
372
|
add: addCommand,
|
|
373
|
+
run: runCommand,
|
|
259
374
|
help: helpCommand,
|
|
260
375
|
};
|
|
261
376
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasak/tool-env",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"author": "dev@saasak.studio",
|
|
6
6
|
"description": "A small util to manage environment variables for your monorepo",
|
|
7
7
|
"keywords": [
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"env"
|
|
11
11
|
],
|
|
12
12
|
"type": "module",
|
|
13
|
-
"main": "
|
|
13
|
+
"main": "src/index.js",
|
|
14
|
+
"types": "src/index.d.ts",
|
|
14
15
|
"files": [
|
|
15
16
|
"bin",
|
|
16
17
|
"src"
|
|
@@ -22,5 +23,12 @@
|
|
|
22
23
|
"execa": "8.0.1",
|
|
23
24
|
"fs-extra": "11.2.0",
|
|
24
25
|
"minimist": "1.2.8"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"vitest": "^1.6.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest"
|
|
25
33
|
}
|
|
26
34
|
}
|