ultraenv 1.0.0 → 1.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.
- package/README.md +67 -1857
- package/dist/{chunk-GC7RXHLA.js → chunk-2MBSLURI.js} +16 -17
- package/dist/{chunk-IGFVP24Q.js → chunk-3AF476D7.js} +1 -1
- package/dist/{chunk-N5PAV4NM.js → chunk-5WUBB633.js} +17 -26
- package/dist/{chunk-2USZPWLZ.js → chunk-6NFA23AY.js} +28 -39
- package/dist/{chunk-AWN6ADV7.js → chunk-6X3BUE5S.js} +45 -54
- package/dist/{chunk-NBOABPHM.js → chunk-72UKVOO5.js} +24 -40
- package/dist/{chunk-JB7RKV3C.js → chunk-7AHG2IP4.js} +1 -7
- package/dist/{chunk-6KS56D6E.js → chunk-7VJ7LK2M.js} +1 -1
- package/dist/{chunk-CIFMBJ4H.js → chunk-BNUHE2ZI.js} +57 -21
- package/dist/{chunk-HFXQGJY3.js → chunk-CVJPO3QY.js} +13 -27
- package/dist/{chunk-IKPTKALB.js → chunk-F7YSINGU.js} +1 -3
- package/dist/{chunk-3VYXPTYV.js → chunk-FSKVYBEP.js} +1 -1
- package/dist/{chunk-4XUYMRK5.js → chunk-GJC64ZG7.js} +4 -3
- package/dist/{chunk-YMMP4VQL.js → chunk-H3QEGEZ6.js} +1 -1
- package/dist/{chunk-CHVO6NWI.js → chunk-LFIKYFPS.js} +2 -2
- package/dist/{chunk-3UV2QNJL.js → chunk-LJSCUOD4.js} +19 -21
- package/dist/{chunk-YVWLXFUT.js → chunk-LQZK6BBQ.js} +2 -2
- package/dist/{chunk-5G2DU52U.js → chunk-N7GOHQBF.js} +7 -1
- package/dist/{chunk-MSXMESFP.js → chunk-OBLMAUCF.js} +8 -14
- package/dist/{chunk-UEWYFN6A.js → chunk-RJTUAMK3.js} +16 -29
- package/dist/{chunk-MNVFG7H4.js → chunk-XPZC32UY.js} +16 -25
- package/dist/{chunk-WMHN5RW2.js → chunk-YLGJQOMM.js} +3 -9
- package/dist/{ci-check-sync-VBMSVWIV.js → ci-check-sync-UO5PARKO.js} +4 -4
- package/dist/{ci-scan-24MT5XGS.js → ci-scan-5D7QBN5X.js} +2 -5
- package/dist/{ci-setup-C2NKEFRD.js → ci-setup-J34DS6KD.js} +2 -2
- package/dist/{ci-validate-7AW24LSQ.js → ci-validate-LWP5NBDN.js} +4 -4
- package/dist/cli/index.cjs +470 -390
- package/dist/cli/index.js +130 -55
- package/dist/comparator-AIRTWBOL.js +13 -0
- package/dist/{config-O5YRQP5Z.js → config-67GDO3CW.js} +3 -3
- package/dist/{debug-PTPXAF3K.js → debug-6VCX3QSP.js} +6 -6
- package/dist/{declaration-LEME4AFZ.js → declaration-YGOVZOXG.js} +3 -3
- package/dist/{doctor-FZAUPKHS.js → doctor-FF7QOTP2.js} +7 -5
- package/dist/{envs-compare-5K3HESX5.js → envs-compare-P7GPKGQX.js} +4 -4
- package/dist/{envs-create-2XXHXMGA.js → envs-create-ISG4SECU.js} +4 -4
- package/dist/{envs-list-NQM5252B.js → envs-list-PUW67HOC.js} +5 -5
- package/dist/{envs-switch-6L2AQYID.js → envs-switch-P4YDJ6LG.js} +4 -4
- package/dist/{envs-validate-FL73Q76T.js → envs-validate-VNKBKYO3.js} +6 -9
- package/dist/{fs-VH7ATUS3.js → fs-7HKOZY5K.js} +2 -2
- package/dist/{generator-LFZBMZZS.js → generator-O23ATCIY.js} +4 -4
- package/dist/{help-3XJBXEHE.js → help-THFLI6YT.js} +108 -26
- package/dist/index.cjs +295 -381
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +81 -100
- package/dist/{init-Y7JQ2KYJ.js → init-KBLVTHQW.js} +15 -11
- package/dist/{install-hook-SKXIV6NV.js → install-hook-42F22BLY.js} +2 -2
- package/dist/{json-schema-I26YNQBH.js → json-schema-YULPWDKA.js} +3 -3
- package/dist/{key-manager-O3G55WPU.js → key-manager-WDWPX3IQ.js} +3 -3
- package/dist/middleware/express.cjs +1 -3
- package/dist/middleware/express.js +1 -1
- package/dist/middleware/fastify.cjs +1 -7
- package/dist/middleware/fastify.js +1 -1
- package/dist/{module-IDIZPP4M.js → module-FGH2V6N2.js} +3 -3
- package/dist/{protect-NCWPM6VC.js → protect-A4G7LQFJ.js} +26 -24
- package/dist/{scan-TRLY36TT.js → scan-4BXGHR33.js} +1 -1
- package/dist/schema/index.cjs +57 -21
- package/dist/schema/index.js +1 -1
- package/dist/{sync-TMHMTLH2.js → sync-MYLMDDY6.js} +23 -14
- package/dist/{typegen-SQOSXBWM.js → typegen-GLBRHWSK.js} +17 -23
- package/dist/{validate-IOAM5HWS.js → validate-J73ETKXD.js} +5 -5
- package/dist/{vault-decrypt-U6HJZNBV.js → vault-decrypt-V3GY5HES.js} +7 -7
- package/dist/{vault-diff-B3ZOQTWI.js → vault-diff-QVE6S6KP.js} +5 -5
- package/dist/{vault-encrypt-GUSLCSKS.js → vault-encrypt-WUBY3OVF.js} +7 -7
- package/dist/{vault-init-GUBOTOUL.js → vault-init-EWSAED44.js} +5 -5
- package/dist/{vault-rekey-DAHT7JCN.js → vault-rekey-VODMGCNA.js} +7 -7
- package/dist/{vault-status-GDLRU2OK.js → vault-status-YXDK6O7X.js} +4 -4
- package/dist/{vault-verify-CD76FJSF.js → vault-verify-SSXGTVBK.js} +7 -7
- package/package.json +1 -1
- package/dist/comparator-RDKX3OI7.js +0 -13
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseEnvFile
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CVJPO3QY.js";
|
|
4
4
|
import {
|
|
5
5
|
exists,
|
|
6
6
|
listFiles,
|
|
7
7
|
readFile,
|
|
8
8
|
writeFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FSKVYBEP.js";
|
|
10
10
|
import {
|
|
11
11
|
FileSystemError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-N7GOHQBF.js";
|
|
13
13
|
|
|
14
14
|
// src/environments/manager.ts
|
|
15
15
|
import { resolve, join } from "path";
|
|
@@ -161,12 +161,14 @@ async function validateAllEnvironments(schema, cwd) {
|
|
|
161
161
|
const message = error instanceof Error ? error.message : String(error);
|
|
162
162
|
results.set(envName ?? pattern, {
|
|
163
163
|
valid: false,
|
|
164
|
-
errors: [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
errors: [
|
|
165
|
+
{
|
|
166
|
+
field: "",
|
|
167
|
+
value: "",
|
|
168
|
+
message: `Failed to read/parse file: ${message}`,
|
|
169
|
+
hint: "Check that the file is a valid .env file."
|
|
170
|
+
}
|
|
171
|
+
],
|
|
170
172
|
warnings: []
|
|
171
173
|
});
|
|
172
174
|
}
|
|
@@ -177,14 +179,11 @@ async function switchEnvironment(envName, cwd) {
|
|
|
177
179
|
const baseDir = resolve(cwd ?? process.cwd());
|
|
178
180
|
const envFile = join(baseDir, `.env.${envName}`);
|
|
179
181
|
if (!await exists(envFile)) {
|
|
180
|
-
throw new FileSystemError(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
hint: `Create a ".env.${envName}" file first, or use "ultraenv env create ${envName}".`
|
|
186
|
-
}
|
|
187
|
-
);
|
|
182
|
+
throw new FileSystemError(`Environment file ".env.${envName}" not found`, {
|
|
183
|
+
path: envFile,
|
|
184
|
+
operation: "read",
|
|
185
|
+
hint: `Create a ".env.${envName}" file first, or use "ultraenv env create ${envName}".`
|
|
186
|
+
});
|
|
188
187
|
}
|
|
189
188
|
const content = await readFile(envFile);
|
|
190
189
|
const header = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EncryptionError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-N7GOHQBF.js";
|
|
4
4
|
|
|
5
5
|
// src/vault/integrity.ts
|
|
6
6
|
import { createHmac, timingSafeEqual as cryptoTimingSafeEqual } from "crypto";
|
|
@@ -8,16 +8,14 @@ var HMAC_ALGORITHM = "sha256";
|
|
|
8
8
|
var HMAC_DIGEST_LENGTH = 64;
|
|
9
9
|
function computeIntegrity(data, key) {
|
|
10
10
|
if (key.length === 0) {
|
|
11
|
-
throw new EncryptionError(
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
);
|
|
11
|
+
throw new EncryptionError("Cannot compute integrity with an empty key", {
|
|
12
|
+
hint: "Provide a valid encryption key for integrity computation."
|
|
13
|
+
});
|
|
15
14
|
}
|
|
16
15
|
if (data.length === 0) {
|
|
17
|
-
throw new EncryptionError(
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
);
|
|
16
|
+
throw new EncryptionError("Cannot compute integrity for empty data", {
|
|
17
|
+
hint: "Provide non-empty data for integrity computation."
|
|
18
|
+
});
|
|
21
19
|
}
|
|
22
20
|
const hmac = createHmac(HMAC_ALGORITHM, key);
|
|
23
21
|
hmac.update(data, "utf-8");
|
|
@@ -25,21 +23,20 @@ function computeIntegrity(data, key) {
|
|
|
25
23
|
}
|
|
26
24
|
function verifyIntegrity(data, key, expected) {
|
|
27
25
|
if (key.length === 0) {
|
|
28
|
-
throw new EncryptionError(
|
|
29
|
-
"Cannot verify integrity with an empty key"
|
|
30
|
-
);
|
|
26
|
+
throw new EncryptionError("Cannot verify integrity with an empty key");
|
|
31
27
|
}
|
|
32
28
|
if (expected.length !== HMAC_DIGEST_LENGTH) {
|
|
33
29
|
throw new EncryptionError(
|
|
34
30
|
`Invalid expected digest length: expected ${HMAC_DIGEST_LENGTH} hex characters, got ${expected.length}`,
|
|
35
|
-
{
|
|
31
|
+
{
|
|
32
|
+
hint: "The integrity digest should be a 64-character lowercase hex string from computeIntegrity()."
|
|
33
|
+
}
|
|
36
34
|
);
|
|
37
35
|
}
|
|
38
36
|
if (!/^[0-9a-f]{64}$/.test(expected)) {
|
|
39
|
-
throw new EncryptionError(
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
);
|
|
37
|
+
throw new EncryptionError("Invalid expected digest format: must be a lowercase hex string", {
|
|
38
|
+
hint: "Ensure the stored digest is a 64-character lowercase hex string."
|
|
39
|
+
});
|
|
43
40
|
}
|
|
44
41
|
try {
|
|
45
42
|
const computed = computeIntegrity(data, key);
|
|
@@ -53,14 +50,10 @@ function verifyIntegrity(data, key, expected) {
|
|
|
53
50
|
}
|
|
54
51
|
function computeVaultChecksum(environments, key) {
|
|
55
52
|
if (key.length === 0) {
|
|
56
|
-
throw new EncryptionError(
|
|
57
|
-
"Cannot compute vault checksum with an empty key"
|
|
58
|
-
);
|
|
53
|
+
throw new EncryptionError("Cannot compute vault checksum with an empty key");
|
|
59
54
|
}
|
|
60
55
|
if (environments.size === 0) {
|
|
61
|
-
throw new EncryptionError(
|
|
62
|
-
"Cannot compute vault checksum for empty environment set"
|
|
63
|
-
);
|
|
56
|
+
throw new EncryptionError("Cannot compute vault checksum for empty environment set");
|
|
64
57
|
}
|
|
65
58
|
const sortedNames = Array.from(environments.keys()).sort();
|
|
66
59
|
const parts = [];
|
|
@@ -76,9 +69,7 @@ function computeVaultChecksum(environments, key) {
|
|
|
76
69
|
}
|
|
77
70
|
function verifyVaultChecksum(environments, key, expected) {
|
|
78
71
|
if (key.length === 0) {
|
|
79
|
-
throw new EncryptionError(
|
|
80
|
-
"Cannot verify vault checksum with an empty key"
|
|
81
|
-
);
|
|
72
|
+
throw new EncryptionError("Cannot verify vault checksum with an empty key");
|
|
82
73
|
}
|
|
83
74
|
if (expected.length !== HMAC_DIGEST_LENGTH) {
|
|
84
75
|
throw new EncryptionError(
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-XC65ORJ5.js";
|
|
5
5
|
import {
|
|
6
6
|
EncryptionError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-N7GOHQBF.js";
|
|
8
8
|
|
|
9
9
|
// src/utils/crypto.ts
|
|
10
10
|
import {
|
|
@@ -38,11 +38,7 @@ function manualHkdf(ikm, salt, info, length) {
|
|
|
38
38
|
const okm = Buffer.alloc(length);
|
|
39
39
|
let previous = Buffer.alloc(0);
|
|
40
40
|
for (let i = 1; i <= n; i++) {
|
|
41
|
-
const hmacData = Buffer.concat([
|
|
42
|
-
previous,
|
|
43
|
-
infoBuffer,
|
|
44
|
-
Buffer.from([i])
|
|
45
|
-
]);
|
|
41
|
+
const hmacData = Buffer.concat([previous, infoBuffer, Buffer.from([i])]);
|
|
46
42
|
previous = createHmac("sha256", prk).update(hmacData).digest();
|
|
47
43
|
const offset = (i - 1) * hashLen;
|
|
48
44
|
const copyLen = Math.min(hashLen, length - offset);
|
|
@@ -59,10 +55,7 @@ function encrypt(key, plaintext) {
|
|
|
59
55
|
const iv = cryptoRandomBytes(12);
|
|
60
56
|
const algo = `aes-${key.length * 8}-gcm`;
|
|
61
57
|
const cipher = createCipheriv(algo, key, iv);
|
|
62
|
-
const ciphertext = Buffer.concat([
|
|
63
|
-
cipher.update(plaintext),
|
|
64
|
-
cipher.final()
|
|
65
|
-
]);
|
|
58
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
66
59
|
const authTag = cipher.getAuthTag();
|
|
67
60
|
return { iv, authTag, ciphertext };
|
|
68
61
|
}
|
|
@@ -76,10 +69,7 @@ function decrypt(key, iv, authTag, ciphertext) {
|
|
|
76
69
|
const decipher = createDecipheriv(algo, key, iv);
|
|
77
70
|
decipher.setAuthTag(authTag);
|
|
78
71
|
try {
|
|
79
|
-
return Buffer.concat([
|
|
80
|
-
decipher.update(ciphertext),
|
|
81
|
-
decipher.final()
|
|
82
|
-
]);
|
|
72
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
83
73
|
} catch (error) {
|
|
84
74
|
const message = error instanceof Error ? error.message : String(error);
|
|
85
75
|
throw new Error(
|
|
@@ -111,14 +101,15 @@ function encryptEnvironment(data, key) {
|
|
|
111
101
|
if (key.length !== 32) {
|
|
112
102
|
throw new EncryptionError(
|
|
113
103
|
`Invalid key length: expected 32 bytes for AES-256, got ${key.length}`,
|
|
114
|
-
{
|
|
104
|
+
{
|
|
105
|
+
hint: "Generate a new key using generateMasterKey() or ensure you are using the correct key file."
|
|
106
|
+
}
|
|
115
107
|
);
|
|
116
108
|
}
|
|
117
109
|
if (Object.keys(data).length === 0) {
|
|
118
|
-
throw new EncryptionError(
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
);
|
|
110
|
+
throw new EncryptionError("Cannot encrypt empty environment data", {
|
|
111
|
+
hint: "Provide at least one environment variable to encrypt."
|
|
112
|
+
});
|
|
122
113
|
}
|
|
123
114
|
try {
|
|
124
115
|
const serialized = serializeEnvData(data);
|
|
@@ -147,7 +138,9 @@ function decryptEnvironment(encrypted, key) {
|
|
|
147
138
|
if (encrypted.algorithm !== ENCRYPTION_ALGORITHM) {
|
|
148
139
|
throw new EncryptionError(
|
|
149
140
|
`Unsupported algorithm: "${encrypted.algorithm}". Expected "${ENCRYPTION_ALGORITHM}".`,
|
|
150
|
-
{
|
|
141
|
+
{
|
|
142
|
+
hint: "This vault was encrypted with a different algorithm. You may need to migrate the vault."
|
|
143
|
+
}
|
|
151
144
|
);
|
|
152
145
|
}
|
|
153
146
|
if (encrypted.iv.length !== IV_LENGTH) {
|
|
@@ -204,12 +197,9 @@ function decryptValue(encrypted, key) {
|
|
|
204
197
|
);
|
|
205
198
|
}
|
|
206
199
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) {
|
|
207
|
-
throw new EncryptionError(
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
hint: `Expected the value to start with "${ENCRYPTED_PREFIX}". This value may not have been encrypted by ultraenv.`
|
|
211
|
-
}
|
|
212
|
-
);
|
|
200
|
+
throw new EncryptionError("Invalid encrypted value format: missing required prefix", {
|
|
201
|
+
hint: `Expected the value to start with "${ENCRYPTED_PREFIX}". This value may not have been encrypted by ultraenv.`
|
|
202
|
+
});
|
|
213
203
|
}
|
|
214
204
|
const payload = encrypted.slice(ENCRYPTED_PREFIX.length);
|
|
215
205
|
const parts = payload.split(":");
|
|
@@ -225,16 +215,14 @@ function decryptValue(encrypted, key) {
|
|
|
225
215
|
const authTagB64 = parts[1];
|
|
226
216
|
const ciphertextB64 = parts[2];
|
|
227
217
|
if (ivB64 === void 0 || authTagB64 === void 0 || ciphertextB64 === void 0) {
|
|
228
|
-
throw new EncryptionError(
|
|
229
|
-
"
|
|
230
|
-
|
|
231
|
-
);
|
|
218
|
+
throw new EncryptionError("Invalid encrypted value: unexpected component structure", {
|
|
219
|
+
hint: "The encrypted data may be corrupted. Try re-encrypting the value."
|
|
220
|
+
});
|
|
232
221
|
}
|
|
233
222
|
if (ivB64.length === 0 || authTagB64.length === 0 || ciphertextB64.length === 0) {
|
|
234
|
-
throw new EncryptionError(
|
|
235
|
-
"
|
|
236
|
-
|
|
237
|
-
);
|
|
223
|
+
throw new EncryptionError("Invalid encrypted value: one or more base64 components are empty", {
|
|
224
|
+
hint: "The encrypted data may be corrupted. Try re-encrypting the value."
|
|
225
|
+
});
|
|
238
226
|
}
|
|
239
227
|
try {
|
|
240
228
|
const iv = base64ToBuffer(ivB64);
|
|
@@ -243,7 +231,9 @@ function decryptValue(encrypted, key) {
|
|
|
243
231
|
if (iv.length !== IV_LENGTH) {
|
|
244
232
|
throw new EncryptionError(
|
|
245
233
|
`Invalid IV length: expected ${IV_LENGTH} bytes, got ${iv.length}`,
|
|
246
|
-
{
|
|
234
|
+
{
|
|
235
|
+
hint: "The encrypted data may be corrupted or was produced by a different version of ultraenv."
|
|
236
|
+
}
|
|
247
237
|
);
|
|
248
238
|
}
|
|
249
239
|
if (authTag.length !== AUTH_TAG_LENGTH) {
|
|
@@ -258,10 +248,9 @@ function decryptValue(encrypted, key) {
|
|
|
258
248
|
if (error instanceof EncryptionError) throw error;
|
|
259
249
|
const message = error instanceof Error ? error.message : String(error);
|
|
260
250
|
if (message.includes("Invalid") || message.includes("base64")) {
|
|
261
|
-
throw new EncryptionError(
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
);
|
|
251
|
+
throw new EncryptionError("Invalid base64 encoding in encrypted value", {
|
|
252
|
+
hint: "The encrypted data may be corrupted. Ensure the value was not modified."
|
|
253
|
+
});
|
|
265
254
|
}
|
|
266
255
|
throw new EncryptionError(
|
|
267
256
|
"Failed to decrypt value. The key may be incorrect or the ciphertext was tampered with.",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseEnvFile
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CVJPO3QY.js";
|
|
4
4
|
import {
|
|
5
5
|
exists,
|
|
6
6
|
readFile,
|
|
7
7
|
removeFile,
|
|
8
8
|
writeFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FSKVYBEP.js";
|
|
10
10
|
import {
|
|
11
11
|
FileSystemError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-N7GOHQBF.js";
|
|
13
13
|
|
|
14
14
|
// src/environments/creator.ts
|
|
15
15
|
import { resolve, join } from "path";
|
|
@@ -173,14 +173,11 @@ function validateEnvironmentName(name) {
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
if (name.length > 64) {
|
|
176
|
-
throw new FileSystemError(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
hint: "Use a shorter environment name."
|
|
182
|
-
}
|
|
183
|
-
);
|
|
176
|
+
throw new FileSystemError(`Environment name too long: ${name.length} characters (max 64)`, {
|
|
177
|
+
path: "",
|
|
178
|
+
operation: "validate",
|
|
179
|
+
hint: "Use a shorter environment name."
|
|
180
|
+
});
|
|
184
181
|
}
|
|
185
182
|
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
186
183
|
throw new FileSystemError(
|
|
@@ -194,14 +191,11 @@ function validateEnvironmentName(name) {
|
|
|
194
191
|
}
|
|
195
192
|
const reserved = ["local", "example", "template", "bak", "backup", "old", "tmp", "temp"];
|
|
196
193
|
if (reserved.includes(name.toLowerCase())) {
|
|
197
|
-
throw new FileSystemError(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
hint: `The name "${name}" is reserved. Choose a different name.`
|
|
203
|
-
}
|
|
204
|
-
);
|
|
194
|
+
throw new FileSystemError(`Reserved environment name: "${name}"`, {
|
|
195
|
+
path: "",
|
|
196
|
+
operation: "validate",
|
|
197
|
+
hint: `The name "${name}" is reserved. Choose a different name.`
|
|
198
|
+
});
|
|
205
199
|
}
|
|
206
200
|
}
|
|
207
201
|
async function createEnvironment(name, options) {
|
|
@@ -209,28 +203,22 @@ async function createEnvironment(name, options) {
|
|
|
209
203
|
const baseDir = resolve(options?.cwd ?? process.cwd());
|
|
210
204
|
const outputPath = join(baseDir, `.env.${name}`);
|
|
211
205
|
if (await exists(outputPath)) {
|
|
212
|
-
throw new FileSystemError(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
hint: "Delete the existing file first, or use a different environment name."
|
|
218
|
-
}
|
|
219
|
-
);
|
|
206
|
+
throw new FileSystemError(`Environment file already exists: ".env.${name}"`, {
|
|
207
|
+
path: outputPath,
|
|
208
|
+
operation: "create",
|
|
209
|
+
hint: "Delete the existing file first, or use a different environment name."
|
|
210
|
+
});
|
|
220
211
|
}
|
|
221
212
|
let content;
|
|
222
213
|
let variables = [];
|
|
223
214
|
if (options?.copyFrom !== void 0) {
|
|
224
215
|
const sourcePath = join(baseDir, `.env.${options.copyFrom}`);
|
|
225
216
|
if (!await exists(sourcePath)) {
|
|
226
|
-
throw new FileSystemError(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
hint: `Ensure ".env.${options.copyFrom}" exists before copying.`
|
|
232
|
-
}
|
|
233
|
-
);
|
|
217
|
+
throw new FileSystemError(`Source environment not found: ".env.${options.copyFrom}"`, {
|
|
218
|
+
path: sourcePath,
|
|
219
|
+
operation: "read",
|
|
220
|
+
hint: `Ensure ".env.${options.copyFrom}" exists before copying.`
|
|
221
|
+
});
|
|
234
222
|
}
|
|
235
223
|
content = await readFile(sourcePath);
|
|
236
224
|
variables = extractVariables(content, options?.schema);
|
|
@@ -251,14 +239,11 @@ async function createEnvironment(name, options) {
|
|
|
251
239
|
} else if (await exists(templatePath)) {
|
|
252
240
|
content = await readFile(templatePath);
|
|
253
241
|
} else {
|
|
254
|
-
throw new FileSystemError(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
hint: "Use a built-in template name (nodejs, nextjs, docker) or a valid file path."
|
|
260
|
-
}
|
|
261
|
-
);
|
|
242
|
+
throw new FileSystemError(`Template not found: "${options.fromTemplate}"`, {
|
|
243
|
+
path: templatePath,
|
|
244
|
+
operation: "read",
|
|
245
|
+
hint: "Use a built-in template name (nodejs, nextjs, docker) or a valid file path."
|
|
246
|
+
});
|
|
262
247
|
}
|
|
263
248
|
variables = extractVariables(content, options?.schema);
|
|
264
249
|
const values = collectValues(variables, options?.values);
|
|
@@ -293,23 +278,29 @@ async function createEnvironment(name, options) {
|
|
|
293
278
|
}
|
|
294
279
|
function listTemplates() {
|
|
295
280
|
return [
|
|
296
|
-
{
|
|
297
|
-
|
|
298
|
-
|
|
281
|
+
{
|
|
282
|
+
name: "nodejs",
|
|
283
|
+
description: "Node.js backend environment (PORT, DATABASE_URL, JWT, CORS, etc.)"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "nextjs",
|
|
287
|
+
description: "Next.js frontend environment (NEXT_PUBLIC_* variables, analytics, etc.)"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "docker",
|
|
291
|
+
description: "Docker deployment environment (container ports, Postgres, Redis, etc.)"
|
|
292
|
+
}
|
|
299
293
|
];
|
|
300
294
|
}
|
|
301
295
|
async function removeEnvironment(name, cwd) {
|
|
302
296
|
const baseDir = resolve(cwd ?? process.cwd());
|
|
303
297
|
const filePath = join(baseDir, `.env.${name}`);
|
|
304
298
|
if (!await exists(filePath)) {
|
|
305
|
-
throw new FileSystemError(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
hint: "The environment does not exist."
|
|
311
|
-
}
|
|
312
|
-
);
|
|
299
|
+
throw new FileSystemError(`Environment file not found: ".env.${name}"`, {
|
|
300
|
+
path: filePath,
|
|
301
|
+
operation: "unlink",
|
|
302
|
+
hint: "The environment does not exist."
|
|
303
|
+
});
|
|
313
304
|
}
|
|
314
305
|
await removeFile(filePath);
|
|
315
306
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readFile,
|
|
3
3
|
writeFile
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FSKVYBEP.js";
|
|
5
5
|
import {
|
|
6
6
|
VaultError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-N7GOHQBF.js";
|
|
8
8
|
|
|
9
9
|
// src/vault/vault-file.ts
|
|
10
10
|
var VAULT_VAR_PREFIX = "ULTRAENV_VAULT_";
|
|
@@ -20,21 +20,14 @@ function parseVaultFile(content) {
|
|
|
20
20
|
if (rawLine === void 0) continue;
|
|
21
21
|
const line = rawLine.trim();
|
|
22
22
|
if (line.length === 0 || line.startsWith("#")) continue;
|
|
23
|
-
const match = line.match(
|
|
24
|
-
new RegExp(`^${VAULT_VAR_PREFIX}([A-Za-z0-9_]+)="(.*)"$`)
|
|
25
|
-
);
|
|
23
|
+
const match = line.match(new RegExp(`^${VAULT_VAR_PREFIX}([A-Za-z0-9_]+)="(.*)"$`));
|
|
26
24
|
if (!match) {
|
|
27
|
-
const plainMatch = line.match(
|
|
28
|
-
new RegExp(`^${VAULT_VAR_PREFIX}([A-Za-z0-9_]+)=(.*)$`)
|
|
29
|
-
);
|
|
25
|
+
const plainMatch = line.match(new RegExp(`^${VAULT_VAR_PREFIX}([A-Za-z0-9_]+)=(.*)$`));
|
|
30
26
|
if (!plainMatch) {
|
|
31
|
-
throw new VaultError(
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
hint: `Each environment entry should be in the format: ${VAULT_VAR_PREFIX}{ENVIRONMENT}="encrypted:..."`
|
|
36
|
-
}
|
|
37
|
-
);
|
|
27
|
+
throw new VaultError(`Invalid vault file format at line ${lineNumber}: "${line}"`, {
|
|
28
|
+
operation: "parse",
|
|
29
|
+
hint: `Each environment entry should be in the format: ${VAULT_VAR_PREFIX}{ENVIRONMENT}="encrypted:..."`
|
|
30
|
+
});
|
|
38
31
|
}
|
|
39
32
|
const envName2 = plainMatch[1].toLowerCase();
|
|
40
33
|
const encryptedValue2 = plainMatch[2];
|
|
@@ -87,41 +80,32 @@ async function readVaultFile(path) {
|
|
|
87
80
|
return parseVaultFile(content);
|
|
88
81
|
} catch (error) {
|
|
89
82
|
if (error instanceof VaultError) throw error;
|
|
90
|
-
throw new VaultError(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/* v8 ignore stop */
|
|
97
|
-
}
|
|
98
|
-
);
|
|
83
|
+
throw new VaultError(`Failed to read vault file "${path}"`, {
|
|
84
|
+
operation: "read",
|
|
85
|
+
/* v8 ignore start */
|
|
86
|
+
cause: error instanceof Error ? error : void 0
|
|
87
|
+
/* v8 ignore stop */
|
|
88
|
+
});
|
|
99
89
|
}
|
|
100
90
|
}
|
|
101
91
|
async function writeVaultFile(path, environments) {
|
|
102
92
|
if (environments.size === 0) {
|
|
103
|
-
throw new VaultError(
|
|
104
|
-
"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
hint: "Add at least one environment to the vault before writing."
|
|
108
|
-
}
|
|
109
|
-
);
|
|
93
|
+
throw new VaultError("Cannot write an empty vault file", {
|
|
94
|
+
operation: "write",
|
|
95
|
+
hint: "Add at least one environment to the vault before writing."
|
|
96
|
+
});
|
|
110
97
|
}
|
|
111
98
|
try {
|
|
112
99
|
const content = serializeVaultFile(environments);
|
|
113
100
|
await writeFile(path, content, "utf-8");
|
|
114
101
|
} catch (error) {
|
|
115
102
|
if (error instanceof VaultError) throw error;
|
|
116
|
-
throw new VaultError(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/* v8 ignore stop */
|
|
123
|
-
}
|
|
124
|
-
);
|
|
103
|
+
throw new VaultError(`Failed to write vault file "${path}"`, {
|
|
104
|
+
operation: "write",
|
|
105
|
+
/* v8 ignore start */
|
|
106
|
+
cause: error instanceof Error ? error : void 0
|
|
107
|
+
/* v8 ignore stop */
|
|
108
|
+
});
|
|
125
109
|
}
|
|
126
110
|
}
|
|
127
111
|
function getEnvironmentData(vault, env) {
|
|
@@ -20,13 +20,7 @@ function ultraenvPlugin(fastify, options = {}, done) {
|
|
|
20
20
|
}
|
|
21
21
|
const allowSet = new Set(allowList.map((k) => k.toUpperCase()));
|
|
22
22
|
const denySet = new Set(denyList.map((k) => k.toUpperCase()));
|
|
23
|
-
const filteredEnv = buildFastifyEnv(
|
|
24
|
-
source,
|
|
25
|
-
allPrefixes,
|
|
26
|
-
allowSet,
|
|
27
|
-
denySet,
|
|
28
|
-
exposeNodeEnv
|
|
29
|
-
);
|
|
23
|
+
const filteredEnv = buildFastifyEnv(source, allPrefixes, allowSet, denySet, exposeNodeEnv);
|
|
30
24
|
fastify.decorate("env", filteredEnv);
|
|
31
25
|
done();
|
|
32
26
|
}
|