@dotenc/cli 0.3.4 → 0.4.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 +393 -38
- package/dist/cli.js +2070 -2
- package/package.json +38 -28
- package/dist/commands/config.js +0 -15
- package/dist/commands/debug.js +0 -16
- package/dist/commands/edit.js +0 -51
- package/dist/commands/init.js +0 -44
- package/dist/commands/key/export.js +0 -21
- package/dist/commands/key/import.js +0 -22
- package/dist/commands/key/rotate.js +0 -35
- package/dist/commands/run.js +0 -46
- package/dist/helpers/createEnvironment.js +0 -10
- package/dist/helpers/createHash.js +0 -7
- package/dist/helpers/createLocalEnvironment.js +0 -21
- package/dist/helpers/createProject.js +0 -13
- package/dist/helpers/crypto.js +0 -70
- package/dist/helpers/environmentExists.js +0 -6
- package/dist/helpers/getDefaultEditor.js +0 -41
- package/dist/helpers/getEnvironmentNameSuggestion.js +0 -5
- package/dist/helpers/homeConfig.js +0 -22
- package/dist/helpers/key.js +0 -39
- package/dist/helpers/parseEnv.js +0 -89
- package/dist/helpers/projectConfig.js +0 -21
- package/dist/program.js +0 -62
- package/dist/prompts/chooseEnvironment.js +0 -18
- package/dist/prompts/createEnvironment.js +0 -12
- package/dist/prompts/inputKey.js +0 -13
- package/dist/tests/e2e.test.js +0 -64
- package/dist/tests/helpers/cleanupProjectKeys.js +0 -13
- package/dist/tests/helpers/waitForFile.js +0 -15
- package/dist/tests/parseEnv.test.js +0 -30
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,2071 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4
|
+
|
|
5
|
+
// package.json
|
|
6
|
+
var package_default;
|
|
7
|
+
var init_package = __esm(() => {
|
|
8
|
+
package_default = {
|
|
9
|
+
name: "@dotenc/cli",
|
|
10
|
+
version: "0.4.2",
|
|
11
|
+
description: "🔐 Git-native encrypted environments powered by your SSH keys",
|
|
12
|
+
author: "Ivan Filho <i@ivanfilho.com>",
|
|
13
|
+
license: "MIT",
|
|
14
|
+
type: "module",
|
|
15
|
+
bin: {
|
|
16
|
+
dotenc: "./dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
files: [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
scripts: {
|
|
22
|
+
dev: "bun src/cli.ts",
|
|
23
|
+
start: "node dist/cli.js",
|
|
24
|
+
build: "bun build src/cli.ts --outdir dist --target node --packages external && { echo '#!/usr/bin/env node'; cat dist/cli.js; } > dist/cli.tmp && mv dist/cli.tmp dist/cli.js",
|
|
25
|
+
"build:binary": "bun run build:binary:darwin-arm64 && bun run build:binary:darwin-x64 && bun run build:binary:linux-x64 && bun run build:binary:linux-arm64 && bun run build:binary:windows-x64",
|
|
26
|
+
"build:binary:darwin-arm64": "bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile dist/dotenc-darwin-arm64",
|
|
27
|
+
"build:binary:darwin-x64": "bun build src/cli.ts --compile --target=bun-darwin-x64 --outfile dist/dotenc-darwin-x64",
|
|
28
|
+
"build:binary:linux-x64": "bun build src/cli.ts --compile --target=bun-linux-x64 --outfile dist/dotenc-linux-x64",
|
|
29
|
+
"build:binary:linux-arm64": "bun build src/cli.ts --compile --target=bun-linux-arm64 --outfile dist/dotenc-linux-arm64",
|
|
30
|
+
"build:binary:windows-x64": "bun build src/cli.ts --compile --target=bun-windows-x64 --outfile dist/dotenc-windows-x64",
|
|
31
|
+
test: "bun test",
|
|
32
|
+
"test:e2e": "docker build -t dotenc-e2e -f e2e/Dockerfile .. && docker run --rm dotenc-e2e"
|
|
33
|
+
},
|
|
34
|
+
devDependencies: {
|
|
35
|
+
"@biomejs/biome": "^2.1.2",
|
|
36
|
+
"@types/bun": "^1.3.9",
|
|
37
|
+
typescript: "^5.8.2"
|
|
38
|
+
},
|
|
39
|
+
dependencies: {
|
|
40
|
+
"@paralleldrive/cuid2": "^2.2.2",
|
|
41
|
+
chalk: "^5.4.1",
|
|
42
|
+
commander: "^13.1.0",
|
|
43
|
+
eciesjs: "^0.4.15",
|
|
44
|
+
inquirer: "^12.4.2",
|
|
45
|
+
zod: "^3.24.2"
|
|
46
|
+
},
|
|
47
|
+
repository: {
|
|
48
|
+
type: "git",
|
|
49
|
+
url: "git+https://github.com/ivanfilhoz/dotenc.git"
|
|
50
|
+
},
|
|
51
|
+
bugs: {
|
|
52
|
+
url: "https://github.com/ivanfilhoz/dotenc/issues"
|
|
53
|
+
},
|
|
54
|
+
homepage: "https://github.com/ivanfilhoz/dotenc#readme",
|
|
55
|
+
keywords: [
|
|
56
|
+
"dotenv",
|
|
57
|
+
"env",
|
|
58
|
+
"environment",
|
|
59
|
+
"variables",
|
|
60
|
+
"secrets",
|
|
61
|
+
"encrypted",
|
|
62
|
+
"encryption",
|
|
63
|
+
"aes-256-gcm",
|
|
64
|
+
"ssh",
|
|
65
|
+
"ssh-keys",
|
|
66
|
+
"ed25519",
|
|
67
|
+
"rsa",
|
|
68
|
+
"git",
|
|
69
|
+
"cli",
|
|
70
|
+
"secure",
|
|
71
|
+
"security",
|
|
72
|
+
"devops",
|
|
73
|
+
"cicd",
|
|
74
|
+
"configuration",
|
|
75
|
+
"config"
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// src/helpers/crypto.ts
|
|
81
|
+
import crypto from "node:crypto";
|
|
82
|
+
async function encryptData(key, input) {
|
|
83
|
+
if (key.length !== 32) {
|
|
84
|
+
throw new Error("Key must be 32 bytes (256 bits) for AES-256-GCM.");
|
|
85
|
+
}
|
|
86
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
87
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
88
|
+
const encrypted = Buffer.concat([cipher.update(input), cipher.final()]);
|
|
89
|
+
const authTag = cipher.getAuthTag();
|
|
90
|
+
return Buffer.concat([iv, encrypted, authTag]);
|
|
91
|
+
}
|
|
92
|
+
async function decryptData(key, input) {
|
|
93
|
+
if (key.length !== 32) {
|
|
94
|
+
throw new Error("Key must be 32 bytes (256 bits) for AES-256-GCM.");
|
|
95
|
+
}
|
|
96
|
+
if (input.length < IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
97
|
+
throw new Error(`Encrypted input is too short (${input.length} bytes). Expected at least ${IV_LENGTH + AUTH_TAG_LENGTH} bytes.`);
|
|
98
|
+
}
|
|
99
|
+
const iv = input.subarray(0, IV_LENGTH);
|
|
100
|
+
const authTag = input.subarray(input.length - AUTH_TAG_LENGTH);
|
|
101
|
+
const ciphertext = input.subarray(IV_LENGTH, input.length - AUTH_TAG_LENGTH);
|
|
102
|
+
try {
|
|
103
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
104
|
+
decipher.setAuthTag(authTag);
|
|
105
|
+
const decrypted = Buffer.concat([
|
|
106
|
+
decipher.update(ciphertext),
|
|
107
|
+
decipher.final()
|
|
108
|
+
]);
|
|
109
|
+
return decrypted.toString();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof Error && error.message.includes("unable to authenticate")) {
|
|
112
|
+
throw new Error(`Failed to decrypt file. This could be because:
|
|
113
|
+
` + `1. The encryption key may be incorrect
|
|
114
|
+
` + `2. The encrypted file may be corrupted
|
|
115
|
+
` + "3. The encrypted file may have been tampered with");
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
var createDataKey = () => crypto.randomBytes(32), ALGORITHM = "aes-256-gcm", IV_LENGTH = 12, AUTH_TAG_LENGTH = 16;
|
|
121
|
+
var init_crypto = () => {};
|
|
122
|
+
|
|
123
|
+
// src/helpers/decryptDataKey.ts
|
|
124
|
+
import crypto2 from "node:crypto";
|
|
125
|
+
import { decrypt, ECIES_CONFIG } from "eciesjs";
|
|
126
|
+
var decryptDataKey = (keyInfo, encryptedDataKey) => {
|
|
127
|
+
if (keyInfo.algorithm === "rsa") {
|
|
128
|
+
return crypto2.privateDecrypt({
|
|
129
|
+
key: keyInfo.privateKey,
|
|
130
|
+
padding: crypto2.constants.RSA_PKCS1_OAEP_PADDING,
|
|
131
|
+
oaepHash: "sha256"
|
|
132
|
+
}, encryptedDataKey);
|
|
133
|
+
}
|
|
134
|
+
if (!keyInfo.rawSeed) {
|
|
135
|
+
throw new Error("Raw seed bytes are required for Ed25519 decryption.");
|
|
136
|
+
}
|
|
137
|
+
return Buffer.from(decrypt(keyInfo.rawSeed, encryptedDataKey));
|
|
138
|
+
};
|
|
139
|
+
var init_decryptDataKey = __esm(() => {
|
|
140
|
+
ECIES_CONFIG.ellipticCurve = "ed25519";
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/helpers/errors.ts
|
|
144
|
+
import chalk from "chalk";
|
|
145
|
+
var passphraseProtectedKeyError = (keys) => `${chalk.red("Error:")} your SSH keys are passphrase-protected, which is not currently supported by dotenc.
|
|
146
|
+
|
|
147
|
+
Passphrase-protected keys found:
|
|
148
|
+
${keys.map((k) => ` - ${k}`).join(`
|
|
149
|
+
`)}
|
|
150
|
+
|
|
151
|
+
To generate a key without a passphrase:
|
|
152
|
+
${chalk.gray('ssh-keygen -t ed25519 -N ""')}
|
|
153
|
+
|
|
154
|
+
Or use an existing key without a passphrase.`;
|
|
155
|
+
var init_errors = () => {};
|
|
156
|
+
|
|
157
|
+
// src/schemas/environment.ts
|
|
158
|
+
import { z } from "zod";
|
|
159
|
+
var environmentSchema;
|
|
160
|
+
var init_environment = __esm(() => {
|
|
161
|
+
environmentSchema = z.object({
|
|
162
|
+
keys: z.array(z.object({
|
|
163
|
+
name: z.string(),
|
|
164
|
+
fingerprint: z.string(),
|
|
165
|
+
encryptedDataKey: z.string(),
|
|
166
|
+
algorithm: z.enum(["rsa", "ed25519"])
|
|
167
|
+
})),
|
|
168
|
+
encryptedContent: z.string()
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/helpers/getEnvironmentByPath.ts
|
|
173
|
+
import fs from "node:fs/promises";
|
|
174
|
+
var getEnvironmentByPath = async (filePath) => {
|
|
175
|
+
let environmentInput;
|
|
176
|
+
try {
|
|
177
|
+
environmentInput = await fs.readFile(filePath, "utf-8");
|
|
178
|
+
} catch (_error) {
|
|
179
|
+
throw new Error(`Environment file not found: ${filePath}`);
|
|
180
|
+
}
|
|
181
|
+
let environmentJson;
|
|
182
|
+
try {
|
|
183
|
+
const rawJson = JSON.parse(environmentInput);
|
|
184
|
+
environmentJson = environmentSchema.parse(rawJson);
|
|
185
|
+
} catch (_error) {
|
|
186
|
+
throw new Error("Failed to parse the environment file. Please ensure it is a valid JSON file.");
|
|
187
|
+
}
|
|
188
|
+
return environmentJson;
|
|
189
|
+
};
|
|
190
|
+
var init_getEnvironmentByPath = __esm(() => {
|
|
191
|
+
init_environment();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// src/helpers/getEnvironmentByName.ts
|
|
195
|
+
import path from "node:path";
|
|
196
|
+
var getEnvironmentByName = async (name) => {
|
|
197
|
+
return getEnvironmentByPath(path.join(process.cwd(), `.env.${name}.enc`));
|
|
198
|
+
};
|
|
199
|
+
var init_getEnvironmentByName = __esm(() => {
|
|
200
|
+
init_getEnvironmentByPath();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// src/helpers/getKeyFingerprint.ts
|
|
204
|
+
import crypto3 from "node:crypto";
|
|
205
|
+
var getKeyFingerprint = (keyInput) => {
|
|
206
|
+
const publicKey = keyInput instanceof crypto3.KeyObject && keyInput.type === "public" ? keyInput : crypto3.createPublicKey(keyInput);
|
|
207
|
+
const der = publicKey.export({ type: "spki", format: "der" });
|
|
208
|
+
const hash = crypto3.createHash("sha256").update(der).digest("hex");
|
|
209
|
+
return hash;
|
|
210
|
+
};
|
|
211
|
+
var init_getKeyFingerprint = () => {};
|
|
212
|
+
|
|
213
|
+
// src/helpers/isPassphraseProtected.ts
|
|
214
|
+
function isPassphraseProtected(keyContent) {
|
|
215
|
+
if (keyContent.includes("BEGIN ENCRYPTED PRIVATE KEY")) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (keyContent.includes("Proc-Type: 4,ENCRYPTED")) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (keyContent.includes("BEGIN OPENSSH PRIVATE KEY")) {
|
|
222
|
+
return isOpenSSHKeyEncrypted(keyContent);
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
function isOpenSSHKeyEncrypted(content) {
|
|
227
|
+
const lines = content.split(`
|
|
228
|
+
`);
|
|
229
|
+
const startIdx = lines.findIndex((l) => l.trim().startsWith("-----BEGIN OPENSSH PRIVATE KEY-----"));
|
|
230
|
+
const endIdx = lines.findIndex((l) => l.trim().startsWith("-----END OPENSSH PRIVATE KEY-----"));
|
|
231
|
+
if (startIdx === -1 || endIdx === -1)
|
|
232
|
+
return false;
|
|
233
|
+
const base64 = lines.slice(startIdx + 1, endIdx).map((l) => l.trim()).join("");
|
|
234
|
+
let buf;
|
|
235
|
+
try {
|
|
236
|
+
buf = Buffer.from(base64, "base64");
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
let offset = 0;
|
|
241
|
+
const MAGIC = "openssh-key-v1\x00";
|
|
242
|
+
if (buf.length < MAGIC.length)
|
|
243
|
+
return false;
|
|
244
|
+
const magic = buf.subarray(0, MAGIC.length).toString("ascii");
|
|
245
|
+
if (magic !== MAGIC)
|
|
246
|
+
return false;
|
|
247
|
+
offset += MAGIC.length;
|
|
248
|
+
if (offset + 4 > buf.length)
|
|
249
|
+
return false;
|
|
250
|
+
const cipherLen = buf.readUInt32BE(offset);
|
|
251
|
+
offset += 4;
|
|
252
|
+
if (offset + cipherLen > buf.length)
|
|
253
|
+
return false;
|
|
254
|
+
const ciphername = buf.subarray(offset, offset + cipherLen).toString("ascii");
|
|
255
|
+
return ciphername !== "none";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/helpers/parseOpenSSHKey.ts
|
|
259
|
+
import crypto4 from "node:crypto";
|
|
260
|
+
function parseOpenSSHPrivateKey(content) {
|
|
261
|
+
const lines = content.split(`
|
|
262
|
+
`);
|
|
263
|
+
const startIdx = lines.findIndex((l) => l.trim().startsWith("-----BEGIN OPENSSH PRIVATE KEY-----"));
|
|
264
|
+
const endIdx = lines.findIndex((l) => l.trim().startsWith("-----END OPENSSH PRIVATE KEY-----"));
|
|
265
|
+
if (startIdx === -1 || endIdx === -1)
|
|
266
|
+
return null;
|
|
267
|
+
const base64 = lines.slice(startIdx + 1, endIdx).map((l) => l.trim()).join("");
|
|
268
|
+
const buf = Buffer.from(base64, "base64");
|
|
269
|
+
let offset = 0;
|
|
270
|
+
const MAGIC = "openssh-key-v1\x00";
|
|
271
|
+
const magic = buf.subarray(0, MAGIC.length).toString("ascii");
|
|
272
|
+
if (magic !== MAGIC)
|
|
273
|
+
return null;
|
|
274
|
+
offset += MAGIC.length;
|
|
275
|
+
const ciphername = readString(buf, offset);
|
|
276
|
+
if (!ciphername)
|
|
277
|
+
return null;
|
|
278
|
+
offset = ciphername.nextOffset;
|
|
279
|
+
if (ciphername.value !== "none")
|
|
280
|
+
return null;
|
|
281
|
+
const kdfname = readString(buf, offset);
|
|
282
|
+
if (!kdfname)
|
|
283
|
+
return null;
|
|
284
|
+
offset = kdfname.nextOffset;
|
|
285
|
+
const kdfoptions = readString(buf, offset);
|
|
286
|
+
if (!kdfoptions)
|
|
287
|
+
return null;
|
|
288
|
+
offset = kdfoptions.nextOffset;
|
|
289
|
+
if (offset + 4 > buf.length)
|
|
290
|
+
return null;
|
|
291
|
+
const numKeys = buf.readUInt32BE(offset);
|
|
292
|
+
offset += 4;
|
|
293
|
+
if (numKeys !== 1)
|
|
294
|
+
return null;
|
|
295
|
+
const pubKeyBlob = readString(buf, offset);
|
|
296
|
+
if (!pubKeyBlob)
|
|
297
|
+
return null;
|
|
298
|
+
offset = pubKeyBlob.nextOffset;
|
|
299
|
+
const privBlob = readBytes(buf, offset);
|
|
300
|
+
if (!privBlob)
|
|
301
|
+
return null;
|
|
302
|
+
const priv = privBlob.value;
|
|
303
|
+
let pOffset = 0;
|
|
304
|
+
if (pOffset + 8 > priv.length)
|
|
305
|
+
return null;
|
|
306
|
+
const check1 = priv.readUInt32BE(pOffset);
|
|
307
|
+
pOffset += 4;
|
|
308
|
+
const check2 = priv.readUInt32BE(pOffset);
|
|
309
|
+
pOffset += 4;
|
|
310
|
+
if (check1 !== check2)
|
|
311
|
+
return null;
|
|
312
|
+
const keyType = readString(priv, pOffset);
|
|
313
|
+
if (!keyType)
|
|
314
|
+
return null;
|
|
315
|
+
pOffset = keyType.nextOffset;
|
|
316
|
+
if (keyType.value === "ssh-ed25519") {
|
|
317
|
+
return parseEd25519(priv, pOffset);
|
|
318
|
+
}
|
|
319
|
+
if (keyType.value === "ssh-rsa") {
|
|
320
|
+
return parseRSA(priv, pOffset);
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
function parseEd25519(priv, offset) {
|
|
325
|
+
const pubKey = readBytes(priv, offset);
|
|
326
|
+
if (!pubKey || pubKey.value.length !== 32)
|
|
327
|
+
return null;
|
|
328
|
+
offset = pubKey.nextOffset;
|
|
329
|
+
const privKey = readBytes(priv, offset);
|
|
330
|
+
if (!privKey || privKey.value.length !== 64)
|
|
331
|
+
return null;
|
|
332
|
+
const seed = privKey.value.subarray(0, 32);
|
|
333
|
+
const pkcs8Prefix = Buffer.from([
|
|
334
|
+
48,
|
|
335
|
+
46,
|
|
336
|
+
2,
|
|
337
|
+
1,
|
|
338
|
+
0,
|
|
339
|
+
48,
|
|
340
|
+
5,
|
|
341
|
+
6,
|
|
342
|
+
3,
|
|
343
|
+
43,
|
|
344
|
+
101,
|
|
345
|
+
112,
|
|
346
|
+
4,
|
|
347
|
+
34,
|
|
348
|
+
4,
|
|
349
|
+
32
|
|
350
|
+
]);
|
|
351
|
+
const der = Buffer.concat([pkcs8Prefix, seed]);
|
|
352
|
+
try {
|
|
353
|
+
return crypto4.createPrivateKey({ key: der, format: "der", type: "pkcs8" });
|
|
354
|
+
} catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function parseRSA(priv, offset) {
|
|
359
|
+
const n = readMpint(priv, offset);
|
|
360
|
+
if (!n)
|
|
361
|
+
return null;
|
|
362
|
+
offset = n.nextOffset;
|
|
363
|
+
const e = readMpint(priv, offset);
|
|
364
|
+
if (!e)
|
|
365
|
+
return null;
|
|
366
|
+
offset = e.nextOffset;
|
|
367
|
+
const d = readMpint(priv, offset);
|
|
368
|
+
if (!d)
|
|
369
|
+
return null;
|
|
370
|
+
offset = d.nextOffset;
|
|
371
|
+
const iqmp = readMpint(priv, offset);
|
|
372
|
+
if (!iqmp)
|
|
373
|
+
return null;
|
|
374
|
+
offset = iqmp.nextOffset;
|
|
375
|
+
const p = readMpint(priv, offset);
|
|
376
|
+
if (!p)
|
|
377
|
+
return null;
|
|
378
|
+
offset = p.nextOffset;
|
|
379
|
+
const q = readMpint(priv, offset);
|
|
380
|
+
if (!q)
|
|
381
|
+
return null;
|
|
382
|
+
const dBig = bufToBigInt(d.value);
|
|
383
|
+
const pBig = bufToBigInt(p.value);
|
|
384
|
+
const qBig = bufToBigInt(q.value);
|
|
385
|
+
const dp = dBig % (pBig - 1n);
|
|
386
|
+
const dq = dBig % (qBig - 1n);
|
|
387
|
+
const jwk = {
|
|
388
|
+
kty: "RSA",
|
|
389
|
+
n: bufToBase64Url(n.value),
|
|
390
|
+
e: bufToBase64Url(e.value),
|
|
391
|
+
d: bufToBase64Url(d.value),
|
|
392
|
+
p: bufToBase64Url(p.value),
|
|
393
|
+
q: bufToBase64Url(q.value),
|
|
394
|
+
dp: bigIntToBase64Url(dp),
|
|
395
|
+
dq: bigIntToBase64Url(dq),
|
|
396
|
+
qi: bufToBase64Url(iqmp.value)
|
|
397
|
+
};
|
|
398
|
+
try {
|
|
399
|
+
return crypto4.createPrivateKey({ key: jwk, format: "jwk" });
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function readUint32(buf, offset) {
|
|
405
|
+
if (offset + 4 > buf.length)
|
|
406
|
+
return null;
|
|
407
|
+
return { value: buf.readUInt32BE(offset), nextOffset: offset + 4 };
|
|
408
|
+
}
|
|
409
|
+
function readBytes(buf, offset) {
|
|
410
|
+
const len = readUint32(buf, offset);
|
|
411
|
+
if (!len)
|
|
412
|
+
return null;
|
|
413
|
+
const end = len.nextOffset + len.value;
|
|
414
|
+
if (end > buf.length)
|
|
415
|
+
return null;
|
|
416
|
+
return {
|
|
417
|
+
value: buf.subarray(len.nextOffset, end),
|
|
418
|
+
nextOffset: end
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function readString(buf, offset) {
|
|
422
|
+
const bytes = readBytes(buf, offset);
|
|
423
|
+
if (!bytes)
|
|
424
|
+
return null;
|
|
425
|
+
return {
|
|
426
|
+
value: bytes.value.toString("ascii"),
|
|
427
|
+
nextOffset: bytes.nextOffset
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function readMpint(buf, offset) {
|
|
431
|
+
const bytes = readBytes(buf, offset);
|
|
432
|
+
if (!bytes)
|
|
433
|
+
return null;
|
|
434
|
+
let value = bytes.value;
|
|
435
|
+
if (value.length > 1 && value[0] === 0) {
|
|
436
|
+
value = value.subarray(1);
|
|
437
|
+
}
|
|
438
|
+
return { value, nextOffset: bytes.nextOffset };
|
|
439
|
+
}
|
|
440
|
+
function bufToBase64Url(buf) {
|
|
441
|
+
return Buffer.from(buf).toString("base64url");
|
|
442
|
+
}
|
|
443
|
+
function bufToBigInt(buf) {
|
|
444
|
+
let result = 0n;
|
|
445
|
+
for (const byte of buf) {
|
|
446
|
+
result = result << 8n | BigInt(byte);
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
function bigIntToBase64Url(n) {
|
|
451
|
+
if (n === 0n)
|
|
452
|
+
return "AA";
|
|
453
|
+
const hex = n.toString(16);
|
|
454
|
+
const padded = hex.length % 2 ? `0${hex}` : hex;
|
|
455
|
+
return Buffer.from(padded, "hex").toString("base64url");
|
|
456
|
+
}
|
|
457
|
+
var init_parseOpenSSHKey = () => {};
|
|
458
|
+
|
|
459
|
+
// src/helpers/getPrivateKeys.ts
|
|
460
|
+
import crypto5 from "node:crypto";
|
|
461
|
+
import { existsSync } from "node:fs";
|
|
462
|
+
import fs2 from "node:fs/promises";
|
|
463
|
+
import os from "node:os";
|
|
464
|
+
import path2 from "node:path";
|
|
465
|
+
function extractEd25519RawKeys(privateKey) {
|
|
466
|
+
const privDer = privateKey.export({ type: "pkcs8", format: "der" });
|
|
467
|
+
const rawSeed = Buffer.from(privDer.subarray(privDer.length - 32));
|
|
468
|
+
const publicKey = crypto5.createPublicKey(privateKey);
|
|
469
|
+
const pubDer = publicKey.export({ type: "spki", format: "der" });
|
|
470
|
+
const rawPublicKey = Buffer.from(pubDer.subarray(pubDer.length - 32));
|
|
471
|
+
return { rawSeed, rawPublicKey };
|
|
472
|
+
}
|
|
473
|
+
function detectAlgorithm(privateKey) {
|
|
474
|
+
const keyType = privateKey.asymmetricKeyType;
|
|
475
|
+
if (keyType === "rsa")
|
|
476
|
+
return "rsa";
|
|
477
|
+
if (keyType === "ed25519")
|
|
478
|
+
return "ed25519";
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
function tryParsePrivateKey(keyContent) {
|
|
482
|
+
try {
|
|
483
|
+
return crypto5.createPrivateKey(keyContent);
|
|
484
|
+
} catch {
|
|
485
|
+
return parseOpenSSHPrivateKey(keyContent);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
var SSH_KEY_FILES, getPrivateKeys = async () => {
|
|
489
|
+
const privateKeys = [];
|
|
490
|
+
const passphraseProtectedKeys = [];
|
|
491
|
+
if (process.env.DOTENC_PRIVATE_KEY) {
|
|
492
|
+
let privateKey = null;
|
|
493
|
+
try {
|
|
494
|
+
privateKey = crypto5.createPrivateKey(process.env.DOTENC_PRIVATE_KEY);
|
|
495
|
+
} catch {
|
|
496
|
+
privateKey = parseOpenSSHPrivateKey(process.env.DOTENC_PRIVATE_KEY);
|
|
497
|
+
}
|
|
498
|
+
if (privateKey) {
|
|
499
|
+
const algorithm = detectAlgorithm(privateKey);
|
|
500
|
+
if (algorithm) {
|
|
501
|
+
const entry = {
|
|
502
|
+
name: "env.DOTENC_PRIVATE_KEY",
|
|
503
|
+
privateKey,
|
|
504
|
+
fingerprint: getKeyFingerprint(privateKey),
|
|
505
|
+
algorithm
|
|
506
|
+
};
|
|
507
|
+
if (algorithm === "ed25519") {
|
|
508
|
+
const { rawSeed, rawPublicKey } = extractEd25519RawKeys(privateKey);
|
|
509
|
+
entry.rawSeed = rawSeed;
|
|
510
|
+
entry.rawPublicKey = rawPublicKey;
|
|
511
|
+
}
|
|
512
|
+
privateKeys.push(entry);
|
|
513
|
+
} else {
|
|
514
|
+
console.error(`Unsupported key type in DOTENC_PRIVATE_KEY: ${privateKey.asymmetricKeyType}. Only RSA and Ed25519 are supported.`);
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
if (isPassphraseProtected(process.env.DOTENC_PRIVATE_KEY)) {
|
|
518
|
+
console.error("Error: the key in DOTENC_PRIVATE_KEY is passphrase-protected, which is not currently supported by dotenc.");
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
console.error("Invalid private key format in DOTENC_PRIVATE_KEY environment variable. Please provide a valid private key (PEM or OpenSSH format).");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const sshDir = path2.join(os.homedir(), ".ssh");
|
|
525
|
+
if (!existsSync(sshDir)) {
|
|
526
|
+
return { keys: privateKeys, passphraseProtectedKeys };
|
|
527
|
+
}
|
|
528
|
+
const files = await fs2.readdir(sshDir);
|
|
529
|
+
const knownFiles = SSH_KEY_FILES.filter((f) => files.includes(f));
|
|
530
|
+
const otherFiles = files.filter((f) => !SSH_KEY_FILES.includes(f) && !f.endsWith(".pub") && !f.startsWith("known_hosts") && !f.startsWith("authorized_keys") && f !== "config");
|
|
531
|
+
for (const fileName of [...knownFiles, ...otherFiles]) {
|
|
532
|
+
const filePath = path2.join(sshDir, fileName);
|
|
533
|
+
let stat;
|
|
534
|
+
try {
|
|
535
|
+
stat = await fs2.stat(filePath);
|
|
536
|
+
} catch {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (!stat.isFile())
|
|
540
|
+
continue;
|
|
541
|
+
let keyContent;
|
|
542
|
+
try {
|
|
543
|
+
keyContent = await fs2.readFile(filePath, "utf-8");
|
|
544
|
+
} catch {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (!keyContent.includes("PRIVATE KEY"))
|
|
548
|
+
continue;
|
|
549
|
+
const privateKey = tryParsePrivateKey(keyContent);
|
|
550
|
+
if (!privateKey) {
|
|
551
|
+
if (isPassphraseProtected(keyContent)) {
|
|
552
|
+
passphraseProtectedKeys.push(fileName);
|
|
553
|
+
}
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const algorithm = detectAlgorithm(privateKey);
|
|
557
|
+
if (!algorithm)
|
|
558
|
+
continue;
|
|
559
|
+
const entry = {
|
|
560
|
+
name: fileName,
|
|
561
|
+
privateKey,
|
|
562
|
+
fingerprint: getKeyFingerprint(privateKey),
|
|
563
|
+
algorithm
|
|
564
|
+
};
|
|
565
|
+
if (algorithm === "ed25519") {
|
|
566
|
+
const { rawSeed, rawPublicKey } = extractEd25519RawKeys(privateKey);
|
|
567
|
+
entry.rawSeed = rawSeed;
|
|
568
|
+
entry.rawPublicKey = rawPublicKey;
|
|
569
|
+
}
|
|
570
|
+
privateKeys.push(entry);
|
|
571
|
+
}
|
|
572
|
+
return { keys: privateKeys, passphraseProtectedKeys };
|
|
573
|
+
};
|
|
574
|
+
var init_getPrivateKeys = __esm(() => {
|
|
575
|
+
init_getKeyFingerprint();
|
|
576
|
+
init_parseOpenSSHKey();
|
|
577
|
+
SSH_KEY_FILES = ["id_ed25519", "id_rsa", "id_ecdsa", "id_dsa"];
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// src/helpers/decryptEnvironment.ts
|
|
581
|
+
import chalk2 from "chalk";
|
|
582
|
+
var decryptEnvironmentData = async (environment) => {
|
|
583
|
+
const { keys: availablePrivateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
584
|
+
if (!availablePrivateKeys.length) {
|
|
585
|
+
if (passphraseProtectedKeys.length > 0) {
|
|
586
|
+
throw new Error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
587
|
+
}
|
|
588
|
+
throw new Error("No private keys found. Please ensure you have SSH keys in ~/.ssh/ or set the DOTENC_PRIVATE_KEY environment variable.");
|
|
589
|
+
}
|
|
590
|
+
let grantedKey;
|
|
591
|
+
let selectedPrivateKey;
|
|
592
|
+
for (const privateKeyEntry of availablePrivateKeys) {
|
|
593
|
+
grantedKey = environment.keys.find((key) => {
|
|
594
|
+
return key.fingerprint === privateKeyEntry.fingerprint;
|
|
595
|
+
});
|
|
596
|
+
if (grantedKey) {
|
|
597
|
+
selectedPrivateKey = privateKeyEntry;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (!grantedKey || !selectedPrivateKey) {
|
|
602
|
+
throw new Error("Access denied to the environment.");
|
|
603
|
+
}
|
|
604
|
+
let dataKey;
|
|
605
|
+
try {
|
|
606
|
+
dataKey = decryptDataKey(selectedPrivateKey, Buffer.from(grantedKey.encryptedDataKey, "base64"));
|
|
607
|
+
} catch (error) {
|
|
608
|
+
throw new Error("Failed to decrypt the data key.", { cause: error });
|
|
609
|
+
}
|
|
610
|
+
const decryptedContent = await decryptData(dataKey, Buffer.from(environment.encryptedContent, "base64"));
|
|
611
|
+
return decryptedContent;
|
|
612
|
+
}, decryptEnvironment = async (name) => {
|
|
613
|
+
const { keys: availablePrivateKeys } = await getPrivateKeys();
|
|
614
|
+
const environmentJson = await getEnvironmentByName(name);
|
|
615
|
+
try {
|
|
616
|
+
return await decryptEnvironmentData(environmentJson);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
if (error instanceof Error && error.message === "Access denied to the environment.") {
|
|
619
|
+
console.error(`You do not have access to this environment.
|
|
620
|
+
|
|
621
|
+
These are your available private keys:
|
|
622
|
+
|
|
623
|
+
${availablePrivateKeys.map((key) => `- ${chalk2.green(key.name)}`).join(`
|
|
624
|
+
`)}
|
|
625
|
+
|
|
626
|
+
Please ask the owners of any of the following keys to grant you access:
|
|
627
|
+
|
|
628
|
+
${environmentJson.keys.map((key) => `- ${chalk2.green(key.name)}`).join(`
|
|
629
|
+
`)}
|
|
630
|
+
`);
|
|
631
|
+
}
|
|
632
|
+
if (error instanceof Error && error.message === "Failed to decrypt the data key.") {
|
|
633
|
+
console.error(`${chalk2.red("Error:")} failed to decrypt the data key. Please ensure you have the correct private key.`);
|
|
634
|
+
}
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
var init_decryptEnvironment = __esm(() => {
|
|
639
|
+
init_crypto();
|
|
640
|
+
init_decryptDataKey();
|
|
641
|
+
init_errors();
|
|
642
|
+
init_getEnvironmentByName();
|
|
643
|
+
init_getPrivateKeys();
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// src/helpers/encryptDataKey.ts
|
|
647
|
+
import crypto6 from "node:crypto";
|
|
648
|
+
import { ECIES_CONFIG as ECIES_CONFIG2, encrypt } from "eciesjs";
|
|
649
|
+
var encryptDataKey = (keyInfo, dataKey) => {
|
|
650
|
+
if (keyInfo.algorithm === "rsa") {
|
|
651
|
+
return crypto6.publicEncrypt({
|
|
652
|
+
key: keyInfo.publicKey,
|
|
653
|
+
padding: crypto6.constants.RSA_PKCS1_OAEP_PADDING,
|
|
654
|
+
oaepHash: "sha256"
|
|
655
|
+
}, dataKey);
|
|
656
|
+
}
|
|
657
|
+
if (!keyInfo.rawPublicKey) {
|
|
658
|
+
throw new Error("Raw public key bytes are required for Ed25519 encryption.");
|
|
659
|
+
}
|
|
660
|
+
return Buffer.from(encrypt(keyInfo.rawPublicKey, dataKey));
|
|
661
|
+
};
|
|
662
|
+
var init_encryptDataKey = __esm(() => {
|
|
663
|
+
ECIES_CONFIG2.ellipticCurve = "ed25519";
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// src/helpers/getPublicKeys.ts
|
|
667
|
+
import crypto7 from "node:crypto";
|
|
668
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
669
|
+
import fs3 from "node:fs/promises";
|
|
670
|
+
import path3 from "node:path";
|
|
671
|
+
function detectAlgorithm2(publicKey) {
|
|
672
|
+
const keyType = publicKey.asymmetricKeyType;
|
|
673
|
+
if (keyType === "rsa")
|
|
674
|
+
return "rsa";
|
|
675
|
+
if (keyType === "ed25519")
|
|
676
|
+
return "ed25519";
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
function extractEd25519RawPublicKey(publicKey) {
|
|
680
|
+
const pubDer = publicKey.export({ type: "spki", format: "der" });
|
|
681
|
+
return Buffer.from(pubDer.subarray(pubDer.length - 32));
|
|
682
|
+
}
|
|
683
|
+
var getPublicKeys = async () => {
|
|
684
|
+
if (!existsSync2(path3.join(process.cwd(), ".dotenc"))) {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const files = await fs3.readdir(path3.join(process.cwd(), ".dotenc"));
|
|
688
|
+
const publicKeys = [];
|
|
689
|
+
for (const fileName of files) {
|
|
690
|
+
if (!fileName.endsWith(".pub")) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const keyInput = await fs3.readFile(path3.join(process.cwd(), ".dotenc", fileName), "utf-8");
|
|
694
|
+
let publicKey;
|
|
695
|
+
try {
|
|
696
|
+
publicKey = crypto7.createPublicKey(keyInput);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error(`Invalid public key format in ${fileName}. Please provide a valid PEM formatted public key.`);
|
|
699
|
+
console.error(`Details: ${error instanceof Error ? error.message : error}`);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const algorithm = detectAlgorithm2(publicKey);
|
|
703
|
+
if (!algorithm) {
|
|
704
|
+
console.error(`Unsupported key type in ${fileName}: ${publicKey.asymmetricKeyType}. Only RSA and Ed25519 are supported.`);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const entry = {
|
|
708
|
+
name: fileName.replace(".pub", ""),
|
|
709
|
+
publicKey,
|
|
710
|
+
fingerprint: getKeyFingerprint(publicKey),
|
|
711
|
+
algorithm
|
|
712
|
+
};
|
|
713
|
+
if (algorithm === "ed25519") {
|
|
714
|
+
entry.rawPublicKey = extractEd25519RawPublicKey(publicKey);
|
|
715
|
+
}
|
|
716
|
+
publicKeys.push(entry);
|
|
717
|
+
}
|
|
718
|
+
return publicKeys;
|
|
719
|
+
};
|
|
720
|
+
var init_getPublicKeys = __esm(() => {
|
|
721
|
+
init_getKeyFingerprint();
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// src/helpers/encryptEnvironment.ts
|
|
725
|
+
import fs4 from "node:fs/promises";
|
|
726
|
+
import path4 from "node:path";
|
|
727
|
+
import chalk3 from "chalk";
|
|
728
|
+
var encryptEnvironment = async (name, newContent, options) => {
|
|
729
|
+
const availablePublicKeys = await getPublicKeys();
|
|
730
|
+
if (!availablePublicKeys.length) {
|
|
731
|
+
throw new Error("No public keys found. Please add a public key using 'dotenc key add'.");
|
|
732
|
+
}
|
|
733
|
+
const environmentJson = await getEnvironmentByName(name);
|
|
734
|
+
const removedKeys = environmentJson.keys.filter((key) => !availablePublicKeys.find((pk) => pk.fingerprint === key.fingerprint));
|
|
735
|
+
if (removedKeys.length) {
|
|
736
|
+
console.log(`The following keys were removed from the environment: ${removedKeys.map((key) => chalk3.red(key.name)).join(", ")}. They will not have access to the new content.`);
|
|
737
|
+
}
|
|
738
|
+
const dataKey = createDataKey();
|
|
739
|
+
const keys = [];
|
|
740
|
+
for (const key of environmentJson.keys) {
|
|
741
|
+
const availableKey = availablePublicKeys.find((pk) => pk.fingerprint === key.fingerprint);
|
|
742
|
+
if (!availableKey) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (options?.revokePublicKeys?.includes(availableKey.name)) {
|
|
746
|
+
console.log(`Public key ${chalk3.green(availableKey.name)} has been revoked from the environment.`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (key.name !== availableKey.name) {
|
|
750
|
+
console.log(`Public key ${chalk3.red(key.name)} renamed to ${chalk3.green(availableKey.name)}.`);
|
|
751
|
+
}
|
|
752
|
+
const encrypted = encryptDataKey(availableKey, dataKey);
|
|
753
|
+
keys.push({
|
|
754
|
+
name: availableKey.name,
|
|
755
|
+
fingerprint: availableKey.fingerprint,
|
|
756
|
+
encryptedDataKey: encrypted.toString("base64"),
|
|
757
|
+
algorithm: availableKey.algorithm
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
if (options?.grantPublicKeys) {
|
|
761
|
+
for (const publicKeyName of options.grantPublicKeys) {
|
|
762
|
+
const publicKey = availablePublicKeys.find((key) => key.name === publicKeyName);
|
|
763
|
+
if (!publicKey) {
|
|
764
|
+
console.error(`${chalk3.red("Error:")} public key ${chalk3.green(publicKeyName)} not found.`);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const existingPublicKey = keys.find((key) => key.fingerprint === publicKey.fingerprint);
|
|
768
|
+
if (existingPublicKey) {
|
|
769
|
+
if (existingPublicKey.name === publicKey.name) {
|
|
770
|
+
console.log(`Public key ${chalk3.green(publicKey.name)} already has access to the environment.`);
|
|
771
|
+
} else {
|
|
772
|
+
console.log(`Public key ${chalk3.red(existingPublicKey.name)} renamed to ${chalk3.green(publicKey.name)}.`);
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const encrypted = encryptDataKey(publicKey, dataKey);
|
|
777
|
+
keys.push({
|
|
778
|
+
name: publicKey.name,
|
|
779
|
+
fingerprint: publicKey.fingerprint,
|
|
780
|
+
encryptedDataKey: encrypted.toString("base64"),
|
|
781
|
+
algorithm: publicKey.algorithm
|
|
782
|
+
});
|
|
783
|
+
console.log(`Public key ${chalk3.green(publicKey.name)} has been granted access to the environment.`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (!keys.length) {
|
|
787
|
+
throw new Error("No valid public keys are left to encrypt the environment. Please ensure you have valid public keys added. Operation aborted.");
|
|
788
|
+
}
|
|
789
|
+
const encryptedContent = await encryptData(dataKey, newContent);
|
|
790
|
+
const newEnvironmentJson = {
|
|
791
|
+
keys,
|
|
792
|
+
encryptedContent: encryptedContent.toString("base64")
|
|
793
|
+
};
|
|
794
|
+
await fs4.writeFile(path4.join(process.cwd(), `.env.${name}.enc`), JSON.stringify(newEnvironmentJson, null, 2), "utf-8");
|
|
795
|
+
};
|
|
796
|
+
var init_encryptEnvironment = __esm(() => {
|
|
797
|
+
init_crypto();
|
|
798
|
+
init_encryptDataKey();
|
|
799
|
+
init_getEnvironmentByName();
|
|
800
|
+
init_getPublicKeys();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// src/helpers/getPublicKeyByName.ts
|
|
804
|
+
import crypto8 from "node:crypto";
|
|
805
|
+
import fs5 from "node:fs/promises";
|
|
806
|
+
import path5 from "node:path";
|
|
807
|
+
var getPublicKeyByName = async (name) => {
|
|
808
|
+
const filePath = path5.join(process.cwd(), ".dotenc", `${name}.pub`);
|
|
809
|
+
let publicKeyInput;
|
|
810
|
+
try {
|
|
811
|
+
await fs5.access(filePath);
|
|
812
|
+
publicKeyInput = await fs5.readFile(filePath, "utf-8");
|
|
813
|
+
} catch (error) {
|
|
814
|
+
throw new Error(`No public key found with name ${name}.`, {
|
|
815
|
+
cause: error
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
return crypto8.createPublicKey(publicKeyInput);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
throw new Error(`Invalid public key format for ${name}. Please provide a valid PEM formatted public key.`, { cause: error });
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
var init_getPublicKeyByName = () => {};
|
|
825
|
+
|
|
826
|
+
// src/helpers/validateEnvironmentName.ts
|
|
827
|
+
var validateEnvironmentName = (name) => {
|
|
828
|
+
if (!name) {
|
|
829
|
+
return { valid: false, reason: "Environment name must not be empty." };
|
|
830
|
+
}
|
|
831
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
|
|
832
|
+
return {
|
|
833
|
+
valid: false,
|
|
834
|
+
reason: `Invalid environment name "${name}". Only letters, numbers, dots, hyphens, and underscores are allowed.`
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
return { valid: true };
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// src/helpers/getEnvironments.ts
|
|
841
|
+
import fs6 from "node:fs/promises";
|
|
842
|
+
var getEnvironments = async () => {
|
|
843
|
+
const files = await fs6.readdir(process.cwd());
|
|
844
|
+
const envFiles = files.filter((file) => file.startsWith(".env.") && file.endsWith(".enc"));
|
|
845
|
+
return envFiles.map((file) => file.slice(5, -4));
|
|
846
|
+
};
|
|
847
|
+
var init_getEnvironments = () => {};
|
|
848
|
+
|
|
849
|
+
// src/prompts/chooseEnvironment.ts
|
|
850
|
+
import inquirer from "inquirer";
|
|
851
|
+
var chooseEnvironmentPrompt = async (message) => {
|
|
852
|
+
const environments = await getEnvironments();
|
|
853
|
+
if (!environments.length) {
|
|
854
|
+
console.log('No environment files found. To create a new environment, run "dotenc env create"');
|
|
855
|
+
}
|
|
856
|
+
const result = await inquirer.prompt([
|
|
857
|
+
{
|
|
858
|
+
type: "list",
|
|
859
|
+
name: "environment",
|
|
860
|
+
message,
|
|
861
|
+
choices: environments
|
|
862
|
+
}
|
|
863
|
+
]);
|
|
864
|
+
return result.environment;
|
|
865
|
+
};
|
|
866
|
+
var init_chooseEnvironment = __esm(() => {
|
|
867
|
+
init_getEnvironments();
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/prompts/choosePublicKey.ts
|
|
871
|
+
import inquirer2 from "inquirer";
|
|
872
|
+
var choosePublicKeyPrompt = async (message, multiple) => {
|
|
873
|
+
const publicKeys = await getPublicKeys();
|
|
874
|
+
const result = await inquirer2.prompt([
|
|
875
|
+
{
|
|
876
|
+
type: multiple ? "list" : "checkbox",
|
|
877
|
+
name: "key",
|
|
878
|
+
message,
|
|
879
|
+
choices: publicKeys.map((key) => key.name.replace(".pub", ""))
|
|
880
|
+
}
|
|
881
|
+
]);
|
|
882
|
+
return result.key;
|
|
883
|
+
};
|
|
884
|
+
var init_choosePublicKey = __esm(() => {
|
|
885
|
+
init_getPublicKeys();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// src/commands/auth/grant.ts
|
|
889
|
+
import chalk4 from "chalk";
|
|
890
|
+
var grantCommand = async (environmentNameArg, publicKeyNameArg) => {
|
|
891
|
+
const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to grant access to?");
|
|
892
|
+
const validation = validateEnvironmentName(environmentName);
|
|
893
|
+
if (!validation.valid) {
|
|
894
|
+
console.error(`${chalk4.red("Error:")} ${validation.reason}`);
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
let currentContent;
|
|
898
|
+
try {
|
|
899
|
+
currentContent = await decryptEnvironment(environmentName);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while decrypting the environment.");
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
let publicKeyName = publicKeyNameArg;
|
|
905
|
+
if (!publicKeyName) {
|
|
906
|
+
publicKeyName = await choosePublicKeyPrompt("Which public key do you want to grant access to this environment?");
|
|
907
|
+
}
|
|
908
|
+
try {
|
|
909
|
+
await getPublicKeyByName(publicKeyName);
|
|
910
|
+
} catch (error) {
|
|
911
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while retrieving the public key.");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
await encryptEnvironment(environmentName, currentContent, {
|
|
916
|
+
grantPublicKeys: [publicKeyName]
|
|
917
|
+
});
|
|
918
|
+
} catch (error) {
|
|
919
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while encrypting the environment.");
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
var init_grant = __esm(() => {
|
|
924
|
+
init_decryptEnvironment();
|
|
925
|
+
init_encryptEnvironment();
|
|
926
|
+
init_getPublicKeyByName();
|
|
927
|
+
init_chooseEnvironment();
|
|
928
|
+
init_choosePublicKey();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// src/commands/auth/list.ts
|
|
932
|
+
import chalk5 from "chalk";
|
|
933
|
+
var authListCommand = async (environmentNameArg) => {
|
|
934
|
+
const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to list access for?");
|
|
935
|
+
const validation = validateEnvironmentName(environmentName);
|
|
936
|
+
if (!validation.valid) {
|
|
937
|
+
console.error(`${chalk5.red("Error:")} ${validation.reason}`);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const environment = await getEnvironmentByName(environmentName);
|
|
941
|
+
if (!environment.keys.length) {
|
|
942
|
+
console.log(`No keys have access to ${environmentName}.`);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
for (const key of environment.keys) {
|
|
946
|
+
console.log(key.name);
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
var init_list = __esm(() => {
|
|
950
|
+
init_getEnvironmentByName();
|
|
951
|
+
init_chooseEnvironment();
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
// src/commands/auth/revoke.ts
|
|
955
|
+
import chalk6 from "chalk";
|
|
956
|
+
var revokeCommand = async (environmentNameArg, publicKeyNameArg) => {
|
|
957
|
+
const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to revoke access from?");
|
|
958
|
+
const validation = validateEnvironmentName(environmentName);
|
|
959
|
+
if (!validation.valid) {
|
|
960
|
+
console.error(`${chalk6.red("Error:")} ${validation.reason}`);
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
let currentContent;
|
|
964
|
+
try {
|
|
965
|
+
currentContent = await decryptEnvironment(environmentName);
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while decrypting the environment.");
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
let publicKeyName = publicKeyNameArg;
|
|
971
|
+
if (!publicKeyName) {
|
|
972
|
+
publicKeyName = await choosePublicKeyPrompt("Which public key do you want to revoke access to this environment?");
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
await getPublicKeyByName(publicKeyName);
|
|
976
|
+
} catch (error) {
|
|
977
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while retrieving the public key.");
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
await encryptEnvironment(environmentName, currentContent, {
|
|
982
|
+
revokePublicKeys: [publicKeyName]
|
|
983
|
+
});
|
|
984
|
+
} catch (error) {
|
|
985
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while encrypting the environment.");
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
var init_revoke = __esm(() => {
|
|
990
|
+
init_decryptEnvironment();
|
|
991
|
+
init_encryptEnvironment();
|
|
992
|
+
init_getPublicKeyByName();
|
|
993
|
+
init_chooseEnvironment();
|
|
994
|
+
init_choosePublicKey();
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
// src/helpers/homeConfig.ts
|
|
998
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
999
|
+
import fs7 from "node:fs/promises";
|
|
1000
|
+
import os2 from "node:os";
|
|
1001
|
+
import path6 from "node:path";
|
|
1002
|
+
import { z as z2 } from "zod";
|
|
1003
|
+
var homeConfigSchema, configPath, setHomeConfig = async (config) => {
|
|
1004
|
+
const parsedConfig = homeConfigSchema.parse(config);
|
|
1005
|
+
await fs7.writeFile(configPath, JSON.stringify(parsedConfig, null, 2), "utf-8");
|
|
1006
|
+
}, getHomeConfig = async () => {
|
|
1007
|
+
if (existsSync3(configPath)) {
|
|
1008
|
+
const config = JSON.parse(await fs7.readFile(configPath, "utf-8"));
|
|
1009
|
+
return homeConfigSchema.parse(config);
|
|
1010
|
+
}
|
|
1011
|
+
return {};
|
|
1012
|
+
};
|
|
1013
|
+
var init_homeConfig = __esm(() => {
|
|
1014
|
+
homeConfigSchema = z2.object({
|
|
1015
|
+
editor: z2.string().nullish()
|
|
1016
|
+
});
|
|
1017
|
+
configPath = path6.join(os2.homedir(), ".dotenc", "config.json");
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// src/commands/config.ts
|
|
1021
|
+
var configCommand = async (key, value, options) => {
|
|
1022
|
+
const config = await getHomeConfig();
|
|
1023
|
+
if (options.remove) {
|
|
1024
|
+
delete config[key];
|
|
1025
|
+
await setHomeConfig(config);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (value) {
|
|
1029
|
+
config[key] = value;
|
|
1030
|
+
await setHomeConfig(config);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
console.log(config[key]);
|
|
1034
|
+
};
|
|
1035
|
+
var init_config = __esm(() => {
|
|
1036
|
+
init_homeConfig();
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
// src/helpers/getCurrentKeyName.ts
|
|
1040
|
+
var getCurrentKeyName = async () => {
|
|
1041
|
+
const { keys: privateKeys } = await getPrivateKeys();
|
|
1042
|
+
const publicKeys = await getPublicKeys();
|
|
1043
|
+
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
1044
|
+
const match = publicKeys.find((pub) => privateFingerprints.has(pub.fingerprint));
|
|
1045
|
+
return match?.name;
|
|
1046
|
+
};
|
|
1047
|
+
var init_getCurrentKeyName = __esm(() => {
|
|
1048
|
+
init_getPrivateKeys();
|
|
1049
|
+
init_getPublicKeys();
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// src/helpers/parseEnv.ts
|
|
1053
|
+
var LINE, parseEnv = (lines) => {
|
|
1054
|
+
const obj = {};
|
|
1055
|
+
lines = lines.replace(/\r\n?/gm, `
|
|
1056
|
+
`);
|
|
1057
|
+
let match;
|
|
1058
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
1059
|
+
const key = match[1];
|
|
1060
|
+
let value = match[2] || "";
|
|
1061
|
+
value = value.trim();
|
|
1062
|
+
const maybeQuote = value[0];
|
|
1063
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
1064
|
+
if (maybeQuote === '"') {
|
|
1065
|
+
value = value.replace(/\\n/g, `
|
|
1066
|
+
`);
|
|
1067
|
+
value = value.replace(/\\r/g, "\r");
|
|
1068
|
+
}
|
|
1069
|
+
obj[key] = value;
|
|
1070
|
+
}
|
|
1071
|
+
return obj;
|
|
1072
|
+
};
|
|
1073
|
+
var init_parseEnv = __esm(() => {
|
|
1074
|
+
LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
// src/commands/run.ts
|
|
1078
|
+
import { spawn } from "node:child_process";
|
|
1079
|
+
import chalk7 from "chalk";
|
|
1080
|
+
var runCommand = async (command, args, options) => {
|
|
1081
|
+
const environmentName = options.env || process.env.DOTENC_ENV;
|
|
1082
|
+
if (!environmentName) {
|
|
1083
|
+
console.error(`No environment provided. Use -e or set DOTENC_ENV to the environment you want to run the command in.
|
|
1084
|
+
To start a new environment, use "dotenc init [environment]".`);
|
|
1085
|
+
process.exit(1);
|
|
1086
|
+
}
|
|
1087
|
+
const environments = environmentName.split(",");
|
|
1088
|
+
for (const env of environments) {
|
|
1089
|
+
const validation = validateEnvironmentName(env);
|
|
1090
|
+
if (!validation.valid) {
|
|
1091
|
+
console.error(`${chalk7.red("Error:")} ${validation.reason}`);
|
|
1092
|
+
process.exit(1);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
let failureCount = 0;
|
|
1096
|
+
const decryptedEnvs = await Promise.all(environments.map(async (environment) => {
|
|
1097
|
+
let content;
|
|
1098
|
+
try {
|
|
1099
|
+
content = await decryptEnvironment(environment);
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
console.error(error instanceof Error ? error.message : `Unknown error occurred while decrypting the environment ${environment}.`);
|
|
1102
|
+
failureCount++;
|
|
1103
|
+
return {};
|
|
1104
|
+
}
|
|
1105
|
+
const decryptedEnv2 = parseEnv(content);
|
|
1106
|
+
return decryptedEnv2;
|
|
1107
|
+
}));
|
|
1108
|
+
if (failureCount === environments.length) {
|
|
1109
|
+
console.error(`${chalk7.red("Error:")} All environments failed to load.`);
|
|
1110
|
+
process.exit(1);
|
|
1111
|
+
}
|
|
1112
|
+
if (failureCount > 0) {
|
|
1113
|
+
console.error(`${chalk7.yellow("Warning:")} ${failureCount} of ${environments.length} environment(s) failed to load.`);
|
|
1114
|
+
}
|
|
1115
|
+
const decryptedEnv = decryptedEnvs.reduce((acc, env) => {
|
|
1116
|
+
return { ...acc, ...env };
|
|
1117
|
+
}, {});
|
|
1118
|
+
const mergedEnv = { ...process.env, ...decryptedEnv };
|
|
1119
|
+
const child = spawn(command, args, {
|
|
1120
|
+
env: mergedEnv,
|
|
1121
|
+
stdio: "inherit"
|
|
1122
|
+
});
|
|
1123
|
+
child.on("exit", (code) => {
|
|
1124
|
+
process.exit(code);
|
|
1125
|
+
});
|
|
1126
|
+
};
|
|
1127
|
+
var init_run = __esm(() => {
|
|
1128
|
+
init_decryptEnvironment();
|
|
1129
|
+
init_parseEnv();
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// src/commands/dev.ts
|
|
1133
|
+
import chalk8 from "chalk";
|
|
1134
|
+
var devCommand = async (command, args) => {
|
|
1135
|
+
const keyName = await getCurrentKeyName();
|
|
1136
|
+
if (!keyName) {
|
|
1137
|
+
console.error(`${chalk8.red("Error:")} could not resolve your identity. Run ${chalk8.gray("dotenc init")} first.`);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
await runCommand(command, args, { env: `development,${keyName}` });
|
|
1141
|
+
};
|
|
1142
|
+
var init_dev = __esm(() => {
|
|
1143
|
+
init_getCurrentKeyName();
|
|
1144
|
+
init_run();
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
// src/helpers/environmentExists.ts
|
|
1148
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1149
|
+
import path7 from "node:path";
|
|
1150
|
+
var environmentExists = (environment) => {
|
|
1151
|
+
const envPath = path7.join(process.cwd(), `.env.${environment}.enc`);
|
|
1152
|
+
return existsSync4(envPath);
|
|
1153
|
+
};
|
|
1154
|
+
var init_environmentExists = () => {};
|
|
1155
|
+
|
|
1156
|
+
// src/helpers/getEnvironmentNameSuggestion.ts
|
|
1157
|
+
var getEnvironmentNameSuggestion = () => {
|
|
1158
|
+
const suggestions = ["development", "staging", "production", "test"].find((env) => !environmentExists(env)) ?? "";
|
|
1159
|
+
return suggestions;
|
|
1160
|
+
};
|
|
1161
|
+
var init_getEnvironmentNameSuggestion = __esm(() => {
|
|
1162
|
+
init_environmentExists();
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
// src/helpers/projectConfig.ts
|
|
1166
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1167
|
+
import fs8 from "node:fs/promises";
|
|
1168
|
+
import path8 from "node:path";
|
|
1169
|
+
import { z as z3 } from "zod";
|
|
1170
|
+
var projectConfigSchema, configPath2, getProjectConfig = async () => {
|
|
1171
|
+
if (existsSync5(configPath2)) {
|
|
1172
|
+
const config = JSON.parse(await fs8.readFile(configPath2, "utf-8"));
|
|
1173
|
+
return projectConfigSchema.parse(config);
|
|
1174
|
+
}
|
|
1175
|
+
return {};
|
|
1176
|
+
};
|
|
1177
|
+
var init_projectConfig = __esm(() => {
|
|
1178
|
+
projectConfigSchema = z3.object({
|
|
1179
|
+
projectId: z3.string()
|
|
1180
|
+
});
|
|
1181
|
+
configPath2 = path8.join(process.cwd(), "dotenc.json");
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
// src/prompts/createEnvironment.ts
|
|
1185
|
+
import inquirer3 from "inquirer";
|
|
1186
|
+
var createEnvironmentPrompt = async (message, defaultValue) => {
|
|
1187
|
+
const result = await inquirer3.prompt([
|
|
1188
|
+
{
|
|
1189
|
+
type: "input",
|
|
1190
|
+
name: "environment",
|
|
1191
|
+
message,
|
|
1192
|
+
default: defaultValue
|
|
1193
|
+
}
|
|
1194
|
+
]);
|
|
1195
|
+
return result.environment;
|
|
1196
|
+
};
|
|
1197
|
+
var init_createEnvironment = () => {};
|
|
1198
|
+
|
|
1199
|
+
// src/commands/env/create.ts
|
|
1200
|
+
import fs9 from "node:fs/promises";
|
|
1201
|
+
import path9 from "node:path";
|
|
1202
|
+
import chalk9 from "chalk";
|
|
1203
|
+
var createCommand = async (environmentNameArg, publicKeyNameArg, initialContent) => {
|
|
1204
|
+
const { projectId } = await getProjectConfig();
|
|
1205
|
+
if (!projectId) {
|
|
1206
|
+
console.error('No project found. Run "dotenc init" to create one.');
|
|
1207
|
+
process.exit(1);
|
|
1208
|
+
}
|
|
1209
|
+
let environmentName = environmentNameArg;
|
|
1210
|
+
if (!environmentName) {
|
|
1211
|
+
environmentName = await createEnvironmentPrompt("What should the environment be named?", getEnvironmentNameSuggestion());
|
|
1212
|
+
}
|
|
1213
|
+
if (!environmentName) {
|
|
1214
|
+
console.error(`${chalk9.red("Error:")} no environment name provided`);
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
const validation = validateEnvironmentName(environmentName);
|
|
1218
|
+
if (!validation.valid) {
|
|
1219
|
+
console.error(`${chalk9.red("Error:")} ${validation.reason}`);
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
if (environmentExists(environmentName)) {
|
|
1223
|
+
console.error(`${chalk9.red("Error:")} environment ${environmentName} already exists. To edit it, use ${chalk9.gray(`dotenc env edit ${environmentName}`)}`);
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
const availablePublicKeys = await getPublicKeys();
|
|
1227
|
+
if (!availablePublicKeys.length) {
|
|
1228
|
+
console.error(`${chalk9.red("Error:")} no public keys found. Please add a public key using ${chalk9.gray("dotenc key add")}.`);
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
const publicKeys = publicKeyNameArg ? [publicKeyNameArg] : await choosePublicKeyPrompt("Which public key(s) do you want to grant access for this environment?", true);
|
|
1232
|
+
const dataKey = createDataKey();
|
|
1233
|
+
const content = initialContent ?? `# ${environmentName} environment
|
|
1234
|
+
`;
|
|
1235
|
+
const encryptedContent = await encryptData(dataKey, content);
|
|
1236
|
+
const environmentJson = {
|
|
1237
|
+
keys: [],
|
|
1238
|
+
encryptedContent: encryptedContent.toString("base64")
|
|
1239
|
+
};
|
|
1240
|
+
for (const publicKeyName of publicKeys) {
|
|
1241
|
+
const publicKey = availablePublicKeys.find((key) => key.name === publicKeyName);
|
|
1242
|
+
if (!publicKey) {
|
|
1243
|
+
console.error(`Public key ${chalk9.cyan(publicKeyName)} not found or invalid.`);
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const encrypted = encryptDataKey(publicKey, dataKey);
|
|
1247
|
+
environmentJson.keys.push({
|
|
1248
|
+
name: publicKeyName,
|
|
1249
|
+
fingerprint: publicKey.fingerprint,
|
|
1250
|
+
encryptedDataKey: encrypted.toString("base64"),
|
|
1251
|
+
algorithm: publicKey.algorithm
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
await fs9.writeFile(path9.join(process.cwd(), `.env.${environmentName}.enc`), JSON.stringify(environmentJson, null, 2), "utf-8");
|
|
1255
|
+
console.log(`${chalk9.green("✔")} Environment ${chalk9.cyan(environmentName)} created!`);
|
|
1256
|
+
console.log(`
|
|
1257
|
+
Some useful tips:`);
|
|
1258
|
+
const editCommand = chalk9.gray(`dotenc env edit ${environmentName}`);
|
|
1259
|
+
console.log(`
|
|
1260
|
+
- To securely edit your environment: ${editCommand}`);
|
|
1261
|
+
const runCommand2 = chalk9.gray(`dotenc run -e ${environmentName} <command> [args...]`);
|
|
1262
|
+
const runCommandWithEnv = chalk9.gray(`DOTENC_ENV=${environmentName} dotenc run <command> [args...]`);
|
|
1263
|
+
console.log(`- To run your application: ${runCommand2} or ${runCommandWithEnv}`);
|
|
1264
|
+
};
|
|
1265
|
+
var init_create = __esm(() => {
|
|
1266
|
+
init_crypto();
|
|
1267
|
+
init_encryptDataKey();
|
|
1268
|
+
init_environmentExists();
|
|
1269
|
+
init_getEnvironmentNameSuggestion();
|
|
1270
|
+
init_getPublicKeys();
|
|
1271
|
+
init_projectConfig();
|
|
1272
|
+
init_choosePublicKey();
|
|
1273
|
+
init_createEnvironment();
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// src/helpers/createHash.ts
|
|
1277
|
+
import crypto9 from "node:crypto";
|
|
1278
|
+
var createHash = (input) => {
|
|
1279
|
+
return crypto9.createHash("sha256").update(input).digest("hex");
|
|
1280
|
+
};
|
|
1281
|
+
var init_createHash = () => {};
|
|
1282
|
+
|
|
1283
|
+
// src/helpers/getDefaultEditor.ts
|
|
1284
|
+
import { execSync } from "node:child_process";
|
|
1285
|
+
var getDefaultEditor = async () => {
|
|
1286
|
+
const config = await getHomeConfig();
|
|
1287
|
+
if (config.editor) {
|
|
1288
|
+
return config.editor;
|
|
1289
|
+
}
|
|
1290
|
+
if (process.env.EDITOR) {
|
|
1291
|
+
return process.env.EDITOR;
|
|
1292
|
+
}
|
|
1293
|
+
if (process.env.VISUAL) {
|
|
1294
|
+
return process.env.VISUAL;
|
|
1295
|
+
}
|
|
1296
|
+
const platform = process.platform;
|
|
1297
|
+
if (platform === "win32") {
|
|
1298
|
+
return "notepad";
|
|
1299
|
+
}
|
|
1300
|
+
const editors = ["nano", "vim", "vi"];
|
|
1301
|
+
for (const editor of editors) {
|
|
1302
|
+
try {
|
|
1303
|
+
execSync(`command -v ${editor}`, { stdio: "ignore" });
|
|
1304
|
+
return editor;
|
|
1305
|
+
} catch {}
|
|
1306
|
+
}
|
|
1307
|
+
throw new Error('No text editor found. Please set the EDITOR environment variable, configure an editor using "dotenc config editor <command>", or install a text editor (e.g., nano, vim, or notepad).');
|
|
1308
|
+
};
|
|
1309
|
+
var init_getDefaultEditor = __esm(() => {
|
|
1310
|
+
init_homeConfig();
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// src/commands/env/edit.ts
|
|
1314
|
+
import { spawnSync } from "node:child_process";
|
|
1315
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1316
|
+
import fs10 from "node:fs/promises";
|
|
1317
|
+
import os3 from "node:os";
|
|
1318
|
+
import path10 from "node:path";
|
|
1319
|
+
import chalk10 from "chalk";
|
|
1320
|
+
var editCommand = async (environmentNameArg) => {
|
|
1321
|
+
const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to edit?");
|
|
1322
|
+
const nameValidation = validateEnvironmentName(environmentName);
|
|
1323
|
+
if (!nameValidation.valid) {
|
|
1324
|
+
console.error(`${chalk10.red("Error:")} ${nameValidation.reason}`);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
const environmentFile = `.env.${environmentName}.enc`;
|
|
1328
|
+
const environmentFilePath = path10.join(process.cwd(), environmentFile);
|
|
1329
|
+
if (!existsSync6(environmentFilePath)) {
|
|
1330
|
+
console.error(`Environment file not found: ${environmentFilePath}`);
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
let environment;
|
|
1334
|
+
let content;
|
|
1335
|
+
try {
|
|
1336
|
+
environment = await getEnvironmentByName(environmentName);
|
|
1337
|
+
content = await decryptEnvironment(environmentName);
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while decrypting the environment.");
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
const separator = `# ---
|
|
1343
|
+
`;
|
|
1344
|
+
content = `# Editing environment: ${environmentName}
|
|
1345
|
+
# This file is encrypted. Do not share it.
|
|
1346
|
+
# Any changes made here will be encrypted and saved back to the environment file.
|
|
1347
|
+
# The following public keys have access to this environment:
|
|
1348
|
+
${environment.keys.map((key) => `# - ${key.name}`).join(`
|
|
1349
|
+
`)}
|
|
1350
|
+
# Use 'dotenc auth grant' and/or 'dotenc auth revoke' to manage access.
|
|
1351
|
+
# Make sure to save your changes before closing the editor.
|
|
1352
|
+
${separator}${content}`;
|
|
1353
|
+
const tempDir = await fs10.mkdtemp(path10.join(os3.tmpdir(), "dotenc-"));
|
|
1354
|
+
const tempFilePath = path10.join(tempDir, `.env.${environmentName}`);
|
|
1355
|
+
await fs10.writeFile(tempFilePath, content, { encoding: "utf-8", mode: 384 });
|
|
1356
|
+
const initialHash = createHash(content);
|
|
1357
|
+
const cleanup = async () => {
|
|
1358
|
+
await fs10.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
1359
|
+
};
|
|
1360
|
+
const onSignal = () => {
|
|
1361
|
+
fs10.rm(tempDir, { recursive: true, force: true }).catch(() => {}).finally(() => process.exit(130));
|
|
1362
|
+
};
|
|
1363
|
+
process.on("SIGINT", onSignal);
|
|
1364
|
+
process.on("SIGTERM", onSignal);
|
|
1365
|
+
const editor = await getDefaultEditor();
|
|
1366
|
+
try {
|
|
1367
|
+
const result = spawnSync(editor, [tempFilePath], { stdio: "inherit" });
|
|
1368
|
+
if (result.error) {
|
|
1369
|
+
throw result.error;
|
|
1370
|
+
}
|
|
1371
|
+
if (result.status !== 0) {
|
|
1372
|
+
console.error(`
|
|
1373
|
+
Editor exited with code ${result.status}`);
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
let newContent = await fs10.readFile(tempFilePath, "utf-8");
|
|
1377
|
+
const finalHash = createHash(newContent);
|
|
1378
|
+
if (initialHash === finalHash) {
|
|
1379
|
+
console.log(`
|
|
1380
|
+
No changes were made to the ${chalk10.cyan(environmentName)} environment.`);
|
|
1381
|
+
} else {
|
|
1382
|
+
const separatorIndex = newContent.indexOf(separator);
|
|
1383
|
+
if (separatorIndex !== -1) {
|
|
1384
|
+
const headerEndIndex = newContent.indexOf(separator) + separator.length;
|
|
1385
|
+
newContent = newContent.slice(headerEndIndex).trim();
|
|
1386
|
+
}
|
|
1387
|
+
await encryptEnvironment(environmentName, newContent);
|
|
1388
|
+
console.log(`
|
|
1389
|
+
Encrypted ${chalk10.cyan(environmentName)} environment and saved it to ${chalk10.gray(environmentFile)}.`);
|
|
1390
|
+
}
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
console.error(`
|
|
1393
|
+
Failed to open editor: ${editor}`);
|
|
1394
|
+
console.error(error instanceof Error ? error.message : error);
|
|
1395
|
+
} finally {
|
|
1396
|
+
process.removeListener("SIGINT", onSignal);
|
|
1397
|
+
process.removeListener("SIGTERM", onSignal);
|
|
1398
|
+
await cleanup();
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
var init_edit = __esm(() => {
|
|
1402
|
+
init_createHash();
|
|
1403
|
+
init_decryptEnvironment();
|
|
1404
|
+
init_encryptEnvironment();
|
|
1405
|
+
init_getDefaultEditor();
|
|
1406
|
+
init_getEnvironmentByName();
|
|
1407
|
+
init_chooseEnvironment();
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
// src/commands/env/list.ts
|
|
1411
|
+
var envListCommand = async () => {
|
|
1412
|
+
const environments = await getEnvironments();
|
|
1413
|
+
if (!environments.length) {
|
|
1414
|
+
console.log("No environments found.");
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
for (const name of environments) {
|
|
1418
|
+
console.log(name);
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
var init_list2 = __esm(() => {
|
|
1422
|
+
init_getEnvironments();
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
// src/commands/env/rotate.ts
|
|
1426
|
+
import chalk11 from "chalk";
|
|
1427
|
+
var rotateCommand = async (environmentNameArg) => {
|
|
1428
|
+
const environmentName = environmentNameArg || await chooseEnvironmentPrompt("What environment do you want to rotate the data key for?");
|
|
1429
|
+
const validation = validateEnvironmentName(environmentName);
|
|
1430
|
+
if (!validation.valid) {
|
|
1431
|
+
console.error(`${chalk11.red("Error:")} ${validation.reason}`);
|
|
1432
|
+
process.exit(1);
|
|
1433
|
+
}
|
|
1434
|
+
let currentContent;
|
|
1435
|
+
try {
|
|
1436
|
+
currentContent = await decryptEnvironment(environmentName);
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while decrypting the environment.");
|
|
1439
|
+
process.exit(1);
|
|
1440
|
+
}
|
|
1441
|
+
try {
|
|
1442
|
+
await encryptEnvironment(environmentName, currentContent);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
console.error(error instanceof Error ? error.message : "Unknown error occurred while encrypting the environment.");
|
|
1445
|
+
process.exit(1);
|
|
1446
|
+
}
|
|
1447
|
+
console.log(`Data key for ${environmentName} has been rotated.`);
|
|
1448
|
+
};
|
|
1449
|
+
var init_rotate = __esm(() => {
|
|
1450
|
+
init_decryptEnvironment();
|
|
1451
|
+
init_encryptEnvironment();
|
|
1452
|
+
init_chooseEnvironment();
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// src/helpers/createProject.ts
|
|
1456
|
+
import { createId } from "@paralleldrive/cuid2";
|
|
1457
|
+
var createProject = async () => {
|
|
1458
|
+
return {
|
|
1459
|
+
projectId: createId()
|
|
1460
|
+
};
|
|
1461
|
+
};
|
|
1462
|
+
var init_createProject = () => {};
|
|
1463
|
+
|
|
1464
|
+
// src/helpers/setupGitDiff.ts
|
|
1465
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1466
|
+
import fs11 from "node:fs";
|
|
1467
|
+
import path11 from "node:path";
|
|
1468
|
+
var setupGitDiff = () => {
|
|
1469
|
+
const gitattributesPath = path11.join(process.cwd(), ".gitattributes");
|
|
1470
|
+
const marker = "*.enc diff=dotenc";
|
|
1471
|
+
let content = "";
|
|
1472
|
+
if (fs11.existsSync(gitattributesPath)) {
|
|
1473
|
+
content = fs11.readFileSync(gitattributesPath, "utf-8");
|
|
1474
|
+
}
|
|
1475
|
+
if (!content.includes(marker)) {
|
|
1476
|
+
const newline = content.length > 0 && !content.endsWith(`
|
|
1477
|
+
`) ? `
|
|
1478
|
+
` : "";
|
|
1479
|
+
fs11.writeFileSync(gitattributesPath, `${content}${newline}${marker}
|
|
1480
|
+
`);
|
|
1481
|
+
}
|
|
1482
|
+
spawnSync2("git", ["config", "--local", "diff.dotenc.textconv", "dotenc textconv"], {
|
|
1483
|
+
stdio: "ignore"
|
|
1484
|
+
});
|
|
1485
|
+
};
|
|
1486
|
+
var init_setupGitDiff = () => {};
|
|
1487
|
+
|
|
1488
|
+
// src/prompts/inputName.ts
|
|
1489
|
+
import inquirer4 from "inquirer";
|
|
1490
|
+
var inputNamePrompt = async (message, defaultValue) => {
|
|
1491
|
+
const result = await inquirer4.prompt([
|
|
1492
|
+
{
|
|
1493
|
+
type: "input",
|
|
1494
|
+
name: "name",
|
|
1495
|
+
message,
|
|
1496
|
+
default: defaultValue,
|
|
1497
|
+
filter: (input) => input.replace(/[^a-zA-Z0-9_-]/g, "").trim().toLocaleLowerCase()
|
|
1498
|
+
}
|
|
1499
|
+
]);
|
|
1500
|
+
return result.name;
|
|
1501
|
+
};
|
|
1502
|
+
var init_inputName = () => {};
|
|
1503
|
+
|
|
1504
|
+
// src/helpers/validatePublicKey.ts
|
|
1505
|
+
function getRsaModulusLength(key) {
|
|
1506
|
+
const der = key.export({ type: "spki", format: "der" });
|
|
1507
|
+
let i = 0;
|
|
1508
|
+
i += 1;
|
|
1509
|
+
i += der[i] & 128 ? (der[i] & 127) + 1 : 1;
|
|
1510
|
+
i += 1;
|
|
1511
|
+
let algLen = 0;
|
|
1512
|
+
if (der[i] & 128) {
|
|
1513
|
+
const n = der[i] & 127;
|
|
1514
|
+
for (let j = 1;j <= n; j++)
|
|
1515
|
+
algLen = algLen << 8 | der[i + j];
|
|
1516
|
+
i += n + 1;
|
|
1517
|
+
} else {
|
|
1518
|
+
algLen = der[i];
|
|
1519
|
+
i += 1;
|
|
1520
|
+
}
|
|
1521
|
+
i += algLen;
|
|
1522
|
+
i += 1;
|
|
1523
|
+
i += der[i] & 128 ? (der[i] & 127) + 1 : 1;
|
|
1524
|
+
i += 1;
|
|
1525
|
+
i += 1;
|
|
1526
|
+
i += der[i] & 128 ? (der[i] & 127) + 1 : 1;
|
|
1527
|
+
i += 1;
|
|
1528
|
+
let modLen = 0;
|
|
1529
|
+
if (der[i] & 128) {
|
|
1530
|
+
const n = der[i] & 127;
|
|
1531
|
+
for (let j = 1;j <= n; j++)
|
|
1532
|
+
modLen = modLen << 8 | der[i + j];
|
|
1533
|
+
i += n + 1;
|
|
1534
|
+
} else {
|
|
1535
|
+
modLen = der[i];
|
|
1536
|
+
i += 1;
|
|
1537
|
+
}
|
|
1538
|
+
if (der[i] === 0)
|
|
1539
|
+
modLen -= 1;
|
|
1540
|
+
return modLen * 8;
|
|
1541
|
+
}
|
|
1542
|
+
function validatePublicKey(key) {
|
|
1543
|
+
const keyType = key.asymmetricKeyType;
|
|
1544
|
+
switch (keyType) {
|
|
1545
|
+
case "rsa": {
|
|
1546
|
+
const modulusLength = getRsaModulusLength(key);
|
|
1547
|
+
if (modulusLength < 2048) {
|
|
1548
|
+
return {
|
|
1549
|
+
valid: false,
|
|
1550
|
+
reason: `RSA key is ${modulusLength} bits, minimum is 2048 bits.`
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
return { valid: true };
|
|
1554
|
+
}
|
|
1555
|
+
case "ed25519":
|
|
1556
|
+
return { valid: true };
|
|
1557
|
+
case "dsa":
|
|
1558
|
+
return {
|
|
1559
|
+
valid: false,
|
|
1560
|
+
reason: "DSA keys are not supported. Use Ed25519 or RSA (2048+ bits)."
|
|
1561
|
+
};
|
|
1562
|
+
case "ec":
|
|
1563
|
+
return {
|
|
1564
|
+
valid: false,
|
|
1565
|
+
reason: "ECDSA keys are not supported. Use Ed25519 or RSA (2048+ bits)."
|
|
1566
|
+
};
|
|
1567
|
+
default:
|
|
1568
|
+
return {
|
|
1569
|
+
valid: false,
|
|
1570
|
+
reason: `Unsupported key type: ${keyType}. Use Ed25519 or RSA (2048+ bits).`
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/prompts/inputKey.ts
|
|
1576
|
+
import inquirer5 from "inquirer";
|
|
1577
|
+
var inputKeyPrompt = async (message, defaultValue) => {
|
|
1578
|
+
const result = await inquirer5.prompt([
|
|
1579
|
+
{
|
|
1580
|
+
type: "password",
|
|
1581
|
+
name: "key",
|
|
1582
|
+
mask: "*",
|
|
1583
|
+
message,
|
|
1584
|
+
default: defaultValue
|
|
1585
|
+
}
|
|
1586
|
+
]);
|
|
1587
|
+
return result.key;
|
|
1588
|
+
};
|
|
1589
|
+
var init_inputKey = () => {};
|
|
1590
|
+
|
|
1591
|
+
// src/commands/key/add.ts
|
|
1592
|
+
import crypto10 from "node:crypto";
|
|
1593
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1594
|
+
import fs12 from "node:fs/promises";
|
|
1595
|
+
import os4 from "node:os";
|
|
1596
|
+
import path12 from "node:path";
|
|
1597
|
+
import chalk12 from "chalk";
|
|
1598
|
+
import inquirer6 from "inquirer";
|
|
1599
|
+
var keyAddCommand = async (nameArg, options) => {
|
|
1600
|
+
const { projectId } = await getProjectConfig();
|
|
1601
|
+
if (!projectId) {
|
|
1602
|
+
console.error('No project found. Run "dotenc init" to create one.');
|
|
1603
|
+
process.exit(1);
|
|
1604
|
+
}
|
|
1605
|
+
let publicKey;
|
|
1606
|
+
if (options?.fromSsh) {
|
|
1607
|
+
const sshPath = options.fromSsh.startsWith("~") ? path12.join(os4.homedir(), options.fromSsh.slice(1)) : options.fromSsh;
|
|
1608
|
+
if (!existsSync7(sshPath)) {
|
|
1609
|
+
console.error(`File ${chalk12.cyan(sshPath)} does not exist. Please provide a valid SSH key path.`);
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
const keyContent = await fs12.readFile(sshPath, "utf-8");
|
|
1613
|
+
if (isPassphraseProtected(keyContent)) {
|
|
1614
|
+
console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
try {
|
|
1618
|
+
const privateKey = crypto10.createPrivateKey(keyContent);
|
|
1619
|
+
publicKey = crypto10.createPublicKey(privateKey);
|
|
1620
|
+
} catch {
|
|
1621
|
+
const parsed = parseOpenSSHPrivateKey(keyContent);
|
|
1622
|
+
if (parsed) {
|
|
1623
|
+
publicKey = crypto10.createPublicKey(parsed);
|
|
1624
|
+
} else {
|
|
1625
|
+
try {
|
|
1626
|
+
publicKey = crypto10.createPublicKey(keyContent);
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
console.error("Invalid SSH key format. Please provide a valid SSH key file.");
|
|
1629
|
+
console.error(`Details: ${error instanceof Error ? error.message : error}`);
|
|
1630
|
+
process.exit(1);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (options?.fromFile) {
|
|
1636
|
+
if (!existsSync7(options.fromFile)) {
|
|
1637
|
+
console.error(`File ${chalk12.cyan(options.fromFile)} does not exist. Please provide a valid file path.`);
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
const keyContent = await fs12.readFile(options.fromFile, "utf-8");
|
|
1641
|
+
if (isPassphraseProtected(keyContent)) {
|
|
1642
|
+
console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
|
|
1643
|
+
process.exit(1);
|
|
1644
|
+
}
|
|
1645
|
+
try {
|
|
1646
|
+
publicKey = crypto10.createPublicKey(keyContent);
|
|
1647
|
+
} catch {
|
|
1648
|
+
try {
|
|
1649
|
+
const privateKey = crypto10.createPrivateKey(keyContent);
|
|
1650
|
+
publicKey = crypto10.createPublicKey(privateKey);
|
|
1651
|
+
} catch {
|
|
1652
|
+
const parsed = parseOpenSSHPrivateKey(keyContent);
|
|
1653
|
+
if (parsed) {
|
|
1654
|
+
publicKey = crypto10.createPublicKey(parsed);
|
|
1655
|
+
} else {
|
|
1656
|
+
console.error("Invalid key format. Please provide a valid PEM formatted public or private key.");
|
|
1657
|
+
process.exit(1);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (options?.fromString) {
|
|
1663
|
+
if (isPassphraseProtected(options.fromString)) {
|
|
1664
|
+
console.error(`${chalk12.red("Error:")} the provided key is passphrase-protected, which is not currently supported by dotenc.`);
|
|
1665
|
+
process.exit(1);
|
|
1666
|
+
}
|
|
1667
|
+
try {
|
|
1668
|
+
publicKey = crypto10.createPublicKey(options.fromString);
|
|
1669
|
+
} catch {
|
|
1670
|
+
try {
|
|
1671
|
+
const privateKey = crypto10.createPrivateKey(options.fromString);
|
|
1672
|
+
publicKey = crypto10.createPublicKey(privateKey);
|
|
1673
|
+
} catch {
|
|
1674
|
+
const parsed = parseOpenSSHPrivateKey(options.fromString);
|
|
1675
|
+
if (parsed) {
|
|
1676
|
+
publicKey = crypto10.createPublicKey(parsed);
|
|
1677
|
+
} else {
|
|
1678
|
+
console.error("Invalid key format. Please provide a valid PEM formatted public or private key.");
|
|
1679
|
+
process.exit(1);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (!publicKey) {
|
|
1685
|
+
const { keys: sshKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
1686
|
+
if (sshKeys.length === 0 && passphraseProtectedKeys.length > 0) {
|
|
1687
|
+
console.warn(`${chalk12.yellow("Warning:")} SSH keys were found but are passphrase-protected (not supported by dotenc):
|
|
1688
|
+
${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
|
|
1689
|
+
`)}
|
|
1690
|
+
`);
|
|
1691
|
+
}
|
|
1692
|
+
const choices = sshKeys.map((key) => ({
|
|
1693
|
+
name: `${key.name} (${key.algorithm})`,
|
|
1694
|
+
value: key.name
|
|
1695
|
+
}));
|
|
1696
|
+
const modePrompt = await inquirer6.prompt({
|
|
1697
|
+
type: "list",
|
|
1698
|
+
name: "mode",
|
|
1699
|
+
message: "Would you like to add one of your SSH keys or paste a public key?",
|
|
1700
|
+
choices: [
|
|
1701
|
+
...choices.length ? [{ name: "Choose from my SSH keys", value: "choose" }] : [],
|
|
1702
|
+
{ name: "Paste a public key (PEM format)", value: "paste" }
|
|
1703
|
+
]
|
|
1704
|
+
});
|
|
1705
|
+
if (modePrompt.mode === "paste") {
|
|
1706
|
+
const publicKeyInput = await inputKeyPrompt("Please paste your public key (PEM format):");
|
|
1707
|
+
if (!publicKeyInput) {
|
|
1708
|
+
console.error("No public key provided. Add operation cancelled.");
|
|
1709
|
+
process.exit(1);
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
publicKey = crypto10.createPublicKey(publicKeyInput);
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
console.error("Invalid public key format. Please provide a valid PEM formatted public key.");
|
|
1715
|
+
console.error(`Details: ${error instanceof Error ? error.message : error}`);
|
|
1716
|
+
process.exit(1);
|
|
1717
|
+
}
|
|
1718
|
+
} else {
|
|
1719
|
+
const keyPrompt = await inquirer6.prompt({
|
|
1720
|
+
type: "list",
|
|
1721
|
+
name: "key",
|
|
1722
|
+
message: "Which SSH key do you want to add?",
|
|
1723
|
+
choices
|
|
1724
|
+
});
|
|
1725
|
+
const selectedKey = sshKeys.find((k) => k.name === keyPrompt.key);
|
|
1726
|
+
if (!selectedKey) {
|
|
1727
|
+
console.error("SSH key not found.");
|
|
1728
|
+
process.exit(1);
|
|
1729
|
+
}
|
|
1730
|
+
publicKey = crypto10.createPublicKey(selectedKey.privateKey);
|
|
1731
|
+
if (!nameArg) {
|
|
1732
|
+
nameArg = selectedKey.name;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (!publicKey) {
|
|
1737
|
+
console.error("An unexpected error occurred. No public key was inferred from the provided input.");
|
|
1738
|
+
process.exit(1);
|
|
1739
|
+
}
|
|
1740
|
+
const validation = validatePublicKey(publicKey);
|
|
1741
|
+
if (!validation.valid) {
|
|
1742
|
+
console.error(validation.reason);
|
|
1743
|
+
process.exit(1);
|
|
1744
|
+
}
|
|
1745
|
+
const publicKeyOutput = publicKey.export({
|
|
1746
|
+
type: "spki",
|
|
1747
|
+
format: "pem"
|
|
1748
|
+
});
|
|
1749
|
+
if (!existsSync7(path12.join(process.cwd(), ".dotenc"))) {
|
|
1750
|
+
await fs12.mkdir(path12.join(process.cwd(), ".dotenc"));
|
|
1751
|
+
}
|
|
1752
|
+
let name = nameArg;
|
|
1753
|
+
if (!name) {
|
|
1754
|
+
name = await inputNamePrompt("What name do you want to give to the new public key?");
|
|
1755
|
+
if (existsSync7(path12.join(process.cwd(), ".dotenc", `${name}.pub`))) {
|
|
1756
|
+
console.error(`A public key with name ${chalk12.cyan(name)} already exists. Please choose a different name.`);
|
|
1757
|
+
process.exit(1);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
await fs12.writeFile(path12.join(process.cwd(), ".dotenc", `${name}.pub`), publicKeyOutput, "utf-8");
|
|
1761
|
+
console.log(`
|
|
1762
|
+
Public key ${chalk12.cyan(name)} added successfully!`);
|
|
1763
|
+
};
|
|
1764
|
+
var init_add = __esm(() => {
|
|
1765
|
+
init_getPrivateKeys();
|
|
1766
|
+
init_parseOpenSSHKey();
|
|
1767
|
+
init_projectConfig();
|
|
1768
|
+
init_inputKey();
|
|
1769
|
+
init_inputName();
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
// src/commands/init.ts
|
|
1773
|
+
import crypto11 from "node:crypto";
|
|
1774
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1775
|
+
import fs13 from "node:fs/promises";
|
|
1776
|
+
import os5 from "node:os";
|
|
1777
|
+
import path13 from "node:path";
|
|
1778
|
+
import chalk13 from "chalk";
|
|
1779
|
+
import inquirer7 from "inquirer";
|
|
1780
|
+
var initCommand = async (options) => {
|
|
1781
|
+
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
1782
|
+
if (!privateKeys.length) {
|
|
1783
|
+
if (passphraseProtectedKeys.length > 0) {
|
|
1784
|
+
console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
1785
|
+
} else {
|
|
1786
|
+
console.error(`${chalk13.red("Error:")} no SSH keys found in ~/.ssh/. Please generate one first using ${chalk13.gray("ssh-keygen")}.`);
|
|
1787
|
+
}
|
|
1788
|
+
process.exit(1);
|
|
1789
|
+
}
|
|
1790
|
+
const username = options.name || await inputNamePrompt("What's your name?", os5.userInfo().username);
|
|
1791
|
+
if (!username) {
|
|
1792
|
+
console.error(`${chalk13.red("Error:")} no name provided.`);
|
|
1793
|
+
process.exit(1);
|
|
1794
|
+
}
|
|
1795
|
+
if (!existsSync8(path13.join(process.cwd(), "dotenc.json"))) {
|
|
1796
|
+
console.log("No project found. Let's create a new one.");
|
|
1797
|
+
try {
|
|
1798
|
+
const { projectId } = await createProject();
|
|
1799
|
+
await fs13.writeFile(path13.join(process.cwd(), "dotenc.json"), JSON.stringify({ projectId }, null, 2), "utf-8");
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
console.error(`${chalk13.red("Error:")} failed to create the project.`);
|
|
1802
|
+
console.error(`${chalk13.red("Details:")} ${error instanceof Error ? error.message : error}`);
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
let keyToAdd;
|
|
1807
|
+
if (privateKeys.length === 1) {
|
|
1808
|
+
keyToAdd = privateKeys[0].name;
|
|
1809
|
+
} else {
|
|
1810
|
+
const result = await inquirer7.prompt([
|
|
1811
|
+
{
|
|
1812
|
+
type: "list",
|
|
1813
|
+
name: "key",
|
|
1814
|
+
message: "Which SSH key would you like to use?",
|
|
1815
|
+
choices: privateKeys.map((key) => ({
|
|
1816
|
+
name: `${key.name} (${key.algorithm})`,
|
|
1817
|
+
value: key.name
|
|
1818
|
+
}))
|
|
1819
|
+
}
|
|
1820
|
+
]);
|
|
1821
|
+
keyToAdd = result.key;
|
|
1822
|
+
}
|
|
1823
|
+
if (!keyToAdd) {
|
|
1824
|
+
console.error(`${chalk13.red("Error:")} no SSH key selected. Please select a key.`);
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1827
|
+
const keyEntry = privateKeys.find((k) => k.name === keyToAdd);
|
|
1828
|
+
if (!keyEntry)
|
|
1829
|
+
process.exit(1);
|
|
1830
|
+
console.log(`Adding key: ${chalk13.cyan(username)} (${keyEntry.algorithm})`);
|
|
1831
|
+
const publicKey = crypto11.createPublicKey(keyEntry.privateKey);
|
|
1832
|
+
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
1833
|
+
await keyAddCommand(username, {
|
|
1834
|
+
fromString: publicKeyPem
|
|
1835
|
+
});
|
|
1836
|
+
try {
|
|
1837
|
+
setupGitDiff();
|
|
1838
|
+
} catch (_error) {
|
|
1839
|
+
console.warn(`${chalk13.yellow("Warning:")} could not set up git diff driver. You can run ${chalk13.gray("dotenc init")} again inside a git repository.`);
|
|
1840
|
+
}
|
|
1841
|
+
let initialContent;
|
|
1842
|
+
const envPath = path13.join(process.cwd(), ".env");
|
|
1843
|
+
if (existsSync8(envPath)) {
|
|
1844
|
+
initialContent = await fs13.readFile(envPath, "utf-8");
|
|
1845
|
+
await fs13.unlink(envPath);
|
|
1846
|
+
console.log(`Migrated ${chalk13.gray(".env")} contents to ${chalk13.cyan(username)} environment.`);
|
|
1847
|
+
}
|
|
1848
|
+
await createCommand(username, username, initialContent);
|
|
1849
|
+
console.log(`
|
|
1850
|
+
${chalk13.green("✔")} Initialization complete!`);
|
|
1851
|
+
console.log(`
|
|
1852
|
+
Some useful tips:`);
|
|
1853
|
+
const editCmd = chalk13.gray(`dotenc env edit ${username}`);
|
|
1854
|
+
console.log(`- To edit your personal environment: ${editCmd}`);
|
|
1855
|
+
const devCmd = chalk13.gray("dotenc dev <command>");
|
|
1856
|
+
console.log(`- To run with your encrypted env: ${devCmd}`);
|
|
1857
|
+
};
|
|
1858
|
+
var init_init = __esm(() => {
|
|
1859
|
+
init_createProject();
|
|
1860
|
+
init_errors();
|
|
1861
|
+
init_getPrivateKeys();
|
|
1862
|
+
init_setupGitDiff();
|
|
1863
|
+
init_inputName();
|
|
1864
|
+
init_create();
|
|
1865
|
+
init_add();
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
// src/commands/key/list.ts
|
|
1869
|
+
var keyListCommand = async () => {
|
|
1870
|
+
const publicKeys = await getPublicKeys();
|
|
1871
|
+
if (!publicKeys.length) {
|
|
1872
|
+
console.log("No public keys found.");
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
for (const key of publicKeys) {
|
|
1876
|
+
console.log(`${key.name} (${key.algorithm})`);
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
var init_list3 = __esm(() => {
|
|
1880
|
+
init_getPublicKeys();
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
// src/prompts/confirm.ts
|
|
1884
|
+
import inquirer8 from "inquirer";
|
|
1885
|
+
var confirmPrompt = async (message) => {
|
|
1886
|
+
const result = await inquirer8.prompt([
|
|
1887
|
+
{
|
|
1888
|
+
type: "confirm",
|
|
1889
|
+
name: "confirm",
|
|
1890
|
+
message
|
|
1891
|
+
}
|
|
1892
|
+
]);
|
|
1893
|
+
return result.confirm;
|
|
1894
|
+
};
|
|
1895
|
+
var init_confirm = () => {};
|
|
1896
|
+
|
|
1897
|
+
// src/commands/key/remove.ts
|
|
1898
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1899
|
+
import fs14 from "node:fs/promises";
|
|
1900
|
+
import path14 from "node:path";
|
|
1901
|
+
import chalk14 from "chalk";
|
|
1902
|
+
var keyRemoveCommand = async (nameArg) => {
|
|
1903
|
+
let name = nameArg;
|
|
1904
|
+
if (!name) {
|
|
1905
|
+
name = await choosePublicKeyPrompt("Which public key do you want to remove?");
|
|
1906
|
+
}
|
|
1907
|
+
const filePath = path14.join(process.cwd(), ".dotenc", `${name}.pub`);
|
|
1908
|
+
if (!existsSync9(filePath)) {
|
|
1909
|
+
console.error(`Public key ${chalk14.cyan(name)} not found.`);
|
|
1910
|
+
process.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
const allEnvironments = await getEnvironments();
|
|
1913
|
+
const affectedEnvironments = [];
|
|
1914
|
+
for (const envName of allEnvironments) {
|
|
1915
|
+
try {
|
|
1916
|
+
const env = await getEnvironmentByName(envName);
|
|
1917
|
+
if (env.keys.some((key) => key.name === name)) {
|
|
1918
|
+
affectedEnvironments.push(envName);
|
|
1919
|
+
}
|
|
1920
|
+
} catch {}
|
|
1921
|
+
}
|
|
1922
|
+
if (affectedEnvironments.length > 0) {
|
|
1923
|
+
console.log(`Key ${chalk14.cyan(name)} has access to the following environments:`);
|
|
1924
|
+
for (const env of affectedEnvironments) {
|
|
1925
|
+
console.log(` - ${env}`);
|
|
1926
|
+
}
|
|
1927
|
+
console.log(`
|
|
1928
|
+
Access will be revoked from these environments automatically.`);
|
|
1929
|
+
}
|
|
1930
|
+
const confirmed = await confirmPrompt(`Are you sure you want to remove key ${name}?`);
|
|
1931
|
+
if (!confirmed) {
|
|
1932
|
+
console.log("Operation cancelled.");
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
await fs14.unlink(filePath);
|
|
1936
|
+
console.log(`Public key ${chalk14.cyan(name)} removed successfully.`);
|
|
1937
|
+
for (const envName of affectedEnvironments) {
|
|
1938
|
+
try {
|
|
1939
|
+
const envJson = await getEnvironmentByName(envName);
|
|
1940
|
+
const content = await decryptEnvironmentData(envJson);
|
|
1941
|
+
await encryptEnvironment(envName, content, {
|
|
1942
|
+
revokePublicKeys: [name]
|
|
1943
|
+
});
|
|
1944
|
+
console.log(`Revoked access from ${chalk14.cyan(envName)} environment.`);
|
|
1945
|
+
} catch {
|
|
1946
|
+
console.warn(`${chalk14.yellow("Warning:")} could not revoke access from ${chalk14.cyan(envName)}. You may need to run ${chalk14.gray(`dotenc auth revoke ${envName} ${name}`)} manually or rotate the environment.`);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
var init_remove = __esm(() => {
|
|
1951
|
+
init_decryptEnvironment();
|
|
1952
|
+
init_encryptEnvironment();
|
|
1953
|
+
init_getEnvironmentByName();
|
|
1954
|
+
init_getEnvironments();
|
|
1955
|
+
init_choosePublicKey();
|
|
1956
|
+
init_confirm();
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
// src/commands/textconv.ts
|
|
1960
|
+
import fs15 from "node:fs/promises";
|
|
1961
|
+
import path15 from "node:path";
|
|
1962
|
+
var textconvCommand = async (filePath) => {
|
|
1963
|
+
const absolutePath = path15.isAbsolute(filePath) ? filePath : path15.join(process.cwd(), filePath);
|
|
1964
|
+
try {
|
|
1965
|
+
const environment = await getEnvironmentByPath(absolutePath);
|
|
1966
|
+
const plaintext = await decryptEnvironmentData(environment);
|
|
1967
|
+
process.stdout.write(plaintext);
|
|
1968
|
+
} catch {
|
|
1969
|
+
const raw = await fs15.readFile(absolutePath, "utf-8");
|
|
1970
|
+
process.stdout.write(raw);
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
var init_textconv = __esm(() => {
|
|
1974
|
+
init_decryptEnvironment();
|
|
1975
|
+
init_getEnvironmentByPath();
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
// src/commands/whoami.ts
|
|
1979
|
+
var whoamiCommand = async () => {
|
|
1980
|
+
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
1981
|
+
const publicKeys = await getPublicKeys();
|
|
1982
|
+
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
1983
|
+
const matchingPublicKey = publicKeys.find((pub) => privateFingerprints.has(pub.fingerprint));
|
|
1984
|
+
if (!matchingPublicKey) {
|
|
1985
|
+
if (privateKeys.length === 0 && passphraseProtectedKeys.length > 0) {
|
|
1986
|
+
console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
1987
|
+
} else {
|
|
1988
|
+
console.error('No matching key found in this project. Run "dotenc init" to set up your identity.');
|
|
1989
|
+
}
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
const matchingPrivateKey = privateKeys.find((pk) => pk.fingerprint === matchingPublicKey.fingerprint);
|
|
1993
|
+
console.log(`Name: ${matchingPublicKey.name}`);
|
|
1994
|
+
console.log(`Active SSH key: ${matchingPrivateKey?.name ?? "unknown"}`);
|
|
1995
|
+
console.log(`Fingerprint: ${matchingPublicKey.fingerprint}`);
|
|
1996
|
+
const environments = await getEnvironments();
|
|
1997
|
+
const authorizedEnvironments = [];
|
|
1998
|
+
for (const envName of environments) {
|
|
1999
|
+
try {
|
|
2000
|
+
const environment = await getEnvironmentByName(envName);
|
|
2001
|
+
const hasAccess = environment.keys.some((key) => key.fingerprint === matchingPublicKey.fingerprint);
|
|
2002
|
+
if (hasAccess) {
|
|
2003
|
+
authorizedEnvironments.push(envName);
|
|
2004
|
+
}
|
|
2005
|
+
} catch {}
|
|
2006
|
+
}
|
|
2007
|
+
if (authorizedEnvironments.length > 0) {
|
|
2008
|
+
console.log("Authorized environments:");
|
|
2009
|
+
for (const env of authorizedEnvironments) {
|
|
2010
|
+
console.log(` - ${env}`);
|
|
2011
|
+
}
|
|
2012
|
+
} else {
|
|
2013
|
+
console.log("Authorized environments: none");
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
var init_whoami = __esm(() => {
|
|
2017
|
+
init_errors();
|
|
2018
|
+
init_getEnvironmentByName();
|
|
2019
|
+
init_getEnvironments();
|
|
2020
|
+
init_getPrivateKeys();
|
|
2021
|
+
init_getPublicKeys();
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
// src/program.ts
|
|
2025
|
+
var exports_program = {};
|
|
2026
|
+
import { Command, Option } from "commander";
|
|
2027
|
+
var program, env, auth, key;
|
|
2028
|
+
var init_program = __esm(() => {
|
|
2029
|
+
init_package();
|
|
2030
|
+
init_grant();
|
|
2031
|
+
init_list();
|
|
2032
|
+
init_revoke();
|
|
2033
|
+
init_config();
|
|
2034
|
+
init_dev();
|
|
2035
|
+
init_create();
|
|
2036
|
+
init_edit();
|
|
2037
|
+
init_list2();
|
|
2038
|
+
init_rotate();
|
|
2039
|
+
init_init();
|
|
2040
|
+
init_add();
|
|
2041
|
+
init_list3();
|
|
2042
|
+
init_remove();
|
|
2043
|
+
init_run();
|
|
2044
|
+
init_textconv();
|
|
2045
|
+
init_whoami();
|
|
2046
|
+
program = new Command;
|
|
2047
|
+
program.name("dotenc").description(package_default.description).version(package_default.version);
|
|
2048
|
+
program.command("init").addOption(new Option("-n, --name <name>", "your username for the project")).description("initialize a dotenc project in the current directory").action(initCommand);
|
|
2049
|
+
env = program.command("env").description("manage environments");
|
|
2050
|
+
env.command("create").argument("[environment]", "the name of the new environment").argument("[publicKey]", "the name of the public key to grant access to the environment").description("create a new environment").action((env2, pubKey) => createCommand(env2, pubKey));
|
|
2051
|
+
env.command("edit").argument("[environment]", "the environment to edit").description("edit an environment").action(editCommand);
|
|
2052
|
+
env.command("rotate").argument("[environment]", "the environment to rotate the data key for").description("rotate the data key for an environment").action(rotateCommand);
|
|
2053
|
+
env.command("list").description("list all environments").action(envListCommand);
|
|
2054
|
+
auth = program.command("auth").description("manage environment access");
|
|
2055
|
+
auth.command("grant").argument("[environment]", "the environment to grant access to").argument("[publicKey]", "the name of the public key to grant access to the environment").description("grant access to an environment").action(grantCommand);
|
|
2056
|
+
auth.command("revoke").argument("[environment]", "the environment to revoke access from").argument("[publicKey]", "the name of the public key to revoke access from the environment").description("revoke access from an environment").action(revokeCommand);
|
|
2057
|
+
auth.command("list").argument("[environment]", "the environment to list access for").description("list keys with access to an environment").action(authListCommand);
|
|
2058
|
+
program.command("run").argument("<command>", "the command to run").argument("[args...]", "the arguments to pass to the command").addOption(new Option("-e, --env <env1>[,env2[,...]]", "the environments to run the command in")).description("run a command in an environment").action(runCommand);
|
|
2059
|
+
program.command("dev").argument("<command>", "the command to run").argument("[args...]", "the arguments to pass to the command").description("shortcut for 'run -e development,<yourname> <command>'").action(devCommand);
|
|
2060
|
+
key = program.command("key").description("manage keys");
|
|
2061
|
+
key.command("add").argument("[name]", "the name of the public key in the project").addOption(new Option("--from-ssh <path>", "add a public key derived from an SSH key file")).addOption(new Option("-f, --from-file <file>", "add the key from a PEM file")).addOption(new Option("-s, --from-string <string>", "add a public key from a string")).description("add a public key to the project").action(keyAddCommand);
|
|
2062
|
+
key.command("list").description("list all public keys in the project").action(keyListCommand);
|
|
2063
|
+
key.command("remove").argument("[name]", "the name of the public key to remove").description("remove a public key from the project").action(keyRemoveCommand);
|
|
2064
|
+
program.command("textconv").argument("<filepath>", "path to the encrypted environment file").description("decrypt an environment file for git diff").action(textconvCommand);
|
|
2065
|
+
program.command("whoami").description("show your identity in this project").action(whoamiCommand);
|
|
2066
|
+
program.command("config").argument("<key>", "the key to get or set").argument("[value]", "the value to set the key to").addOption(new Option("-r, --remove", "remove a configuration key")).description("manage global configuration").action(configCommand);
|
|
2067
|
+
program.parse();
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
// src/cli.ts
|
|
2071
|
+
Promise.resolve().then(() => init_program());
|