@dotenc/cli 0.4.2 → 0.4.4
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 +8 -5
- package/dist/cli.js +82 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
[![NPM Version][npm-image]][npm-url]
|
|
4
4
|
[![Github License][license-image]](LICENSE)
|
|
5
5
|
[![NPM Downloads][downloads-image]][npm-url]
|
|
6
|
+
[![Codecov][codecov-image]][codecov-url]
|
|
6
7
|
|
|
7
8
|
🔐 Git-native encrypted environments powered by your SSH keys
|
|
8
9
|
|
|
9
10
|
## 30-Second Example
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
dotenc init
|
|
13
|
-
dotenc env edit alice
|
|
14
|
-
dotenc dev npm start
|
|
13
|
+
dotenc init # pick your SSH key, choose a name
|
|
14
|
+
dotenc env edit alice # add your personal secrets
|
|
15
|
+
dotenc dev npm start # run with your encrypted env
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Encrypted `.env.alice.enc` committed.
|
|
@@ -103,7 +104,7 @@ Your SSH private keys never leave `~/.ssh/`. dotenc reads them in place - nothin
|
|
|
103
104
|
|
|
104
105
|
After setup, your project will look like:
|
|
105
106
|
|
|
106
|
-
```
|
|
107
|
+
```plaintext
|
|
107
108
|
.
|
|
108
109
|
├── .dotenc/
|
|
109
110
|
│ ├── alice.pub
|
|
@@ -332,7 +333,7 @@ Copy the **entire** contents of the private key file and store it as a secret en
|
|
|
332
333
|
cat ci_key
|
|
333
334
|
```
|
|
334
335
|
|
|
335
|
-
```
|
|
336
|
+
```plaintext
|
|
336
337
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
337
338
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbm...
|
|
338
339
|
-----END OPENSSH PRIVATE KEY-----
|
|
@@ -478,3 +479,5 @@ It does not aim to replace centralized secret managers like Vault or Doppler —
|
|
|
478
479
|
[license-image]: https://img.shields.io/github/license/ivanfilhoz/dotenc.svg
|
|
479
480
|
[downloads-image]: https://img.shields.io/npm/dm/@dotenc/cli.svg
|
|
480
481
|
[npm-url]: https://npmjs.org/package/@dotenc/cli
|
|
482
|
+
[codecov-image]: https://codecov.io/gh/ivanfilhoz/dotenc/graph/badge.svg?token=U2MKXVGBA0
|
|
483
|
+
[codecov-url]: https://codecov.io/gh/ivanfilhoz/dotenc
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ var package_default;
|
|
|
7
7
|
var init_package = __esm(() => {
|
|
8
8
|
package_default = {
|
|
9
9
|
name: "@dotenc/cli",
|
|
10
|
-
version: "0.4.
|
|
10
|
+
version: "0.4.4",
|
|
11
11
|
description: "🔐 Git-native encrypted environments powered by your SSH keys",
|
|
12
12
|
author: "Ivan Filho <i@ivanfilho.com>",
|
|
13
13
|
license: "MIT",
|
|
@@ -579,8 +579,8 @@ var init_getPrivateKeys = __esm(() => {
|
|
|
579
579
|
|
|
580
580
|
// src/helpers/decryptEnvironment.ts
|
|
581
581
|
import chalk2 from "chalk";
|
|
582
|
-
var decryptEnvironmentData = async (environment) => {
|
|
583
|
-
const { keys: availablePrivateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
582
|
+
var defaultDecryptEnvironmentDataDeps, decryptEnvironmentData = async (environment, deps = defaultDecryptEnvironmentDataDeps) => {
|
|
583
|
+
const { keys: availablePrivateKeys, passphraseProtectedKeys } = await deps.getPrivateKeys();
|
|
584
584
|
if (!availablePrivateKeys.length) {
|
|
585
585
|
if (passphraseProtectedKeys.length > 0) {
|
|
586
586
|
throw new Error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
@@ -603,20 +603,20 @@ var decryptEnvironmentData = async (environment) => {
|
|
|
603
603
|
}
|
|
604
604
|
let dataKey;
|
|
605
605
|
try {
|
|
606
|
-
dataKey = decryptDataKey(selectedPrivateKey, Buffer.from(grantedKey.encryptedDataKey, "base64"));
|
|
606
|
+
dataKey = deps.decryptDataKey(selectedPrivateKey, Buffer.from(grantedKey.encryptedDataKey, "base64"));
|
|
607
607
|
} catch (error) {
|
|
608
608
|
throw new Error("Failed to decrypt the data key.", { cause: error });
|
|
609
609
|
}
|
|
610
|
-
const decryptedContent = await decryptData(dataKey, Buffer.from(environment.encryptedContent, "base64"));
|
|
610
|
+
const decryptedContent = await deps.decryptData(dataKey, Buffer.from(environment.encryptedContent, "base64"));
|
|
611
611
|
return decryptedContent;
|
|
612
|
-
}, decryptEnvironment = async (name) => {
|
|
613
|
-
const { keys: availablePrivateKeys } = await getPrivateKeys();
|
|
614
|
-
const environmentJson = await getEnvironmentByName(name);
|
|
612
|
+
}, defaultDecryptEnvironmentDeps, decryptEnvironment = async (name, deps = defaultDecryptEnvironmentDeps) => {
|
|
613
|
+
const { keys: availablePrivateKeys } = await deps.getPrivateKeys();
|
|
614
|
+
const environmentJson = await deps.getEnvironmentByName(name);
|
|
615
615
|
try {
|
|
616
|
-
return await decryptEnvironmentData(environmentJson);
|
|
616
|
+
return await decryptEnvironmentData(environmentJson, deps);
|
|
617
617
|
} catch (error) {
|
|
618
618
|
if (error instanceof Error && error.message === "Access denied to the environment.") {
|
|
619
|
-
|
|
619
|
+
deps.logError(`You do not have access to this environment.
|
|
620
620
|
|
|
621
621
|
These are your available private keys:
|
|
622
622
|
|
|
@@ -630,7 +630,7 @@ var decryptEnvironmentData = async (environment) => {
|
|
|
630
630
|
`);
|
|
631
631
|
}
|
|
632
632
|
if (error instanceof Error && error.message === "Failed to decrypt the data key.") {
|
|
633
|
-
|
|
633
|
+
deps.logError(`${chalk2.red("Error:")} failed to decrypt the data key. Please ensure you have the correct private key.`);
|
|
634
634
|
}
|
|
635
635
|
throw error;
|
|
636
636
|
}
|
|
@@ -641,6 +641,16 @@ var init_decryptEnvironment = __esm(() => {
|
|
|
641
641
|
init_errors();
|
|
642
642
|
init_getEnvironmentByName();
|
|
643
643
|
init_getPrivateKeys();
|
|
644
|
+
defaultDecryptEnvironmentDataDeps = {
|
|
645
|
+
getPrivateKeys,
|
|
646
|
+
decryptDataKey,
|
|
647
|
+
decryptData
|
|
648
|
+
};
|
|
649
|
+
defaultDecryptEnvironmentDeps = {
|
|
650
|
+
...defaultDecryptEnvironmentDataDeps,
|
|
651
|
+
getEnvironmentByName,
|
|
652
|
+
logError: (message) => console.error(message)
|
|
653
|
+
};
|
|
644
654
|
});
|
|
645
655
|
|
|
646
656
|
// src/helpers/encryptDataKey.ts
|
|
@@ -1000,10 +1010,12 @@ import fs7 from "node:fs/promises";
|
|
|
1000
1010
|
import os2 from "node:os";
|
|
1001
1011
|
import path6 from "node:path";
|
|
1002
1012
|
import { z as z2 } from "zod";
|
|
1003
|
-
var homeConfigSchema,
|
|
1013
|
+
var homeConfigSchema, getConfigPath = () => path6.join(os2.homedir(), ".dotenc", "config.json"), setHomeConfig = async (config) => {
|
|
1004
1014
|
const parsedConfig = homeConfigSchema.parse(config);
|
|
1015
|
+
const configPath = getConfigPath();
|
|
1005
1016
|
await fs7.writeFile(configPath, JSON.stringify(parsedConfig, null, 2), "utf-8");
|
|
1006
1017
|
}, getHomeConfig = async () => {
|
|
1018
|
+
const configPath = getConfigPath();
|
|
1007
1019
|
if (existsSync3(configPath)) {
|
|
1008
1020
|
const config = JSON.parse(await fs7.readFile(configPath, "utf-8"));
|
|
1009
1021
|
return homeConfigSchema.parse(config);
|
|
@@ -1014,7 +1026,6 @@ var init_homeConfig = __esm(() => {
|
|
|
1014
1026
|
homeConfigSchema = z2.object({
|
|
1015
1027
|
editor: z2.string().nullish()
|
|
1016
1028
|
});
|
|
1017
|
-
configPath = path6.join(os2.homedir(), ".dotenc", "config.json");
|
|
1018
1029
|
});
|
|
1019
1030
|
|
|
1020
1031
|
// src/commands/config.ts
|
|
@@ -1037,9 +1048,9 @@ var init_config = __esm(() => {
|
|
|
1037
1048
|
});
|
|
1038
1049
|
|
|
1039
1050
|
// src/helpers/getCurrentKeyName.ts
|
|
1040
|
-
var getCurrentKeyName = async () => {
|
|
1041
|
-
const { keys: privateKeys } = await getPrivateKeys();
|
|
1042
|
-
const publicKeys = await getPublicKeys();
|
|
1051
|
+
var getCurrentKeyName = async (deps = { getPrivateKeys, getPublicKeys }) => {
|
|
1052
|
+
const { keys: privateKeys } = await deps.getPrivateKeys();
|
|
1053
|
+
const publicKeys = await deps.getPublicKeys();
|
|
1043
1054
|
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
1044
1055
|
const match = publicKeys.find((pub) => privateFingerprints.has(pub.fingerprint));
|
|
1045
1056
|
return match?.name;
|
|
@@ -1077,71 +1088,85 @@ var init_parseEnv = __esm(() => {
|
|
|
1077
1088
|
// src/commands/run.ts
|
|
1078
1089
|
import { spawn } from "node:child_process";
|
|
1079
1090
|
import chalk7 from "chalk";
|
|
1080
|
-
var runCommand = async (command, args, options) => {
|
|
1091
|
+
var defaultRunCommandDeps, runCommand = async (command, args, options, _command, deps = defaultRunCommandDeps) => {
|
|
1081
1092
|
const environmentName = options.env || process.env.DOTENC_ENV;
|
|
1082
1093
|
if (!environmentName) {
|
|
1083
|
-
|
|
1094
|
+
deps.logError(`No environment provided. Use -e or set DOTENC_ENV to the environment you want to run the command in.
|
|
1084
1095
|
To start a new environment, use "dotenc init [environment]".`);
|
|
1085
|
-
|
|
1096
|
+
deps.exit(1);
|
|
1086
1097
|
}
|
|
1087
1098
|
const environments = environmentName.split(",");
|
|
1088
1099
|
for (const env of environments) {
|
|
1089
|
-
const validation = validateEnvironmentName(env);
|
|
1100
|
+
const validation = deps.validateEnvironmentName(env);
|
|
1090
1101
|
if (!validation.valid) {
|
|
1091
|
-
|
|
1092
|
-
|
|
1102
|
+
deps.logError(`${chalk7.red("Error:")} ${validation.reason}`);
|
|
1103
|
+
deps.exit(1);
|
|
1093
1104
|
}
|
|
1094
1105
|
}
|
|
1095
1106
|
let failureCount = 0;
|
|
1096
1107
|
const decryptedEnvs = await Promise.all(environments.map(async (environment) => {
|
|
1097
1108
|
let content;
|
|
1098
1109
|
try {
|
|
1099
|
-
content = await decryptEnvironment(environment);
|
|
1110
|
+
content = await deps.decryptEnvironment(environment);
|
|
1100
1111
|
} catch (error) {
|
|
1101
|
-
|
|
1112
|
+
deps.logError(error instanceof Error ? error.message : `Unknown error occurred while decrypting the environment ${environment}.`);
|
|
1102
1113
|
failureCount++;
|
|
1103
1114
|
return {};
|
|
1104
1115
|
}
|
|
1105
|
-
const decryptedEnv2 = parseEnv(content);
|
|
1116
|
+
const decryptedEnv2 = deps.parseEnv(content);
|
|
1106
1117
|
return decryptedEnv2;
|
|
1107
1118
|
}));
|
|
1108
1119
|
if (failureCount === environments.length) {
|
|
1109
|
-
|
|
1110
|
-
|
|
1120
|
+
deps.logError(`${chalk7.red("Error:")} All environments failed to load.`);
|
|
1121
|
+
deps.exit(1);
|
|
1111
1122
|
}
|
|
1112
1123
|
if (failureCount > 0) {
|
|
1113
|
-
|
|
1124
|
+
deps.logError(`${chalk7.yellow("Warning:")} ${failureCount} of ${environments.length} environment(s) failed to load.`);
|
|
1114
1125
|
}
|
|
1115
1126
|
const decryptedEnv = decryptedEnvs.reduce((acc, env) => {
|
|
1116
1127
|
return { ...acc, ...env };
|
|
1117
1128
|
}, {});
|
|
1118
1129
|
const mergedEnv = { ...process.env, ...decryptedEnv };
|
|
1119
|
-
const child = spawn(command, args, {
|
|
1130
|
+
const child = deps.spawn(command, args, {
|
|
1120
1131
|
env: mergedEnv,
|
|
1121
1132
|
stdio: "inherit"
|
|
1122
1133
|
});
|
|
1123
1134
|
child.on("exit", (code) => {
|
|
1124
|
-
|
|
1135
|
+
deps.exit(code ?? 0);
|
|
1125
1136
|
});
|
|
1126
1137
|
};
|
|
1127
1138
|
var init_run = __esm(() => {
|
|
1128
1139
|
init_decryptEnvironment();
|
|
1129
1140
|
init_parseEnv();
|
|
1141
|
+
defaultRunCommandDeps = {
|
|
1142
|
+
decryptEnvironment,
|
|
1143
|
+
parseEnv,
|
|
1144
|
+
validateEnvironmentName,
|
|
1145
|
+
spawn,
|
|
1146
|
+
logError: (message) => console.error(message),
|
|
1147
|
+
exit: (code) => process.exit(code)
|
|
1148
|
+
};
|
|
1130
1149
|
});
|
|
1131
1150
|
|
|
1132
1151
|
// src/commands/dev.ts
|
|
1133
1152
|
import chalk8 from "chalk";
|
|
1134
|
-
var devCommand = async (command, args) => {
|
|
1135
|
-
const keyName = await getCurrentKeyName();
|
|
1153
|
+
var defaultDevCommandDeps, devCommand = async (command, args, deps = defaultDevCommandDeps) => {
|
|
1154
|
+
const keyName = await deps.getCurrentKeyName();
|
|
1136
1155
|
if (!keyName) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1156
|
+
deps.logError(`${chalk8.red("Error:")} could not resolve your identity. Run ${chalk8.gray("dotenc init")} first.`);
|
|
1157
|
+
deps.exit(1);
|
|
1139
1158
|
}
|
|
1140
|
-
await runCommand(command, args, { env: `development,${keyName}` });
|
|
1159
|
+
await deps.runCommand(command, args, { env: `development,${keyName}` });
|
|
1141
1160
|
};
|
|
1142
1161
|
var init_dev = __esm(() => {
|
|
1143
1162
|
init_getCurrentKeyName();
|
|
1144
1163
|
init_run();
|
|
1164
|
+
defaultDevCommandDeps = {
|
|
1165
|
+
getCurrentKeyName,
|
|
1166
|
+
runCommand,
|
|
1167
|
+
logError: console.error,
|
|
1168
|
+
exit: process.exit
|
|
1169
|
+
};
|
|
1145
1170
|
});
|
|
1146
1171
|
|
|
1147
1172
|
// src/helpers/environmentExists.ts
|
|
@@ -1167,9 +1192,9 @@ import { existsSync as existsSync5 } from "node:fs";
|
|
|
1167
1192
|
import fs8 from "node:fs/promises";
|
|
1168
1193
|
import path8 from "node:path";
|
|
1169
1194
|
import { z as z3 } from "zod";
|
|
1170
|
-
var projectConfigSchema,
|
|
1171
|
-
if (existsSync5(
|
|
1172
|
-
const config = JSON.parse(await fs8.readFile(
|
|
1195
|
+
var projectConfigSchema, configPath, getProjectConfig = async () => {
|
|
1196
|
+
if (existsSync5(configPath)) {
|
|
1197
|
+
const config = JSON.parse(await fs8.readFile(configPath, "utf-8"));
|
|
1173
1198
|
return projectConfigSchema.parse(config);
|
|
1174
1199
|
}
|
|
1175
1200
|
return {};
|
|
@@ -1178,7 +1203,7 @@ var init_projectConfig = __esm(() => {
|
|
|
1178
1203
|
projectConfigSchema = z3.object({
|
|
1179
1204
|
projectId: z3.string()
|
|
1180
1205
|
});
|
|
1181
|
-
|
|
1206
|
+
configPath = path8.join(process.cwd(), "dotenc.json");
|
|
1182
1207
|
});
|
|
1183
1208
|
|
|
1184
1209
|
// src/prompts/createEnvironment.ts
|
|
@@ -1282,8 +1307,15 @@ var init_createHash = () => {};
|
|
|
1282
1307
|
|
|
1283
1308
|
// src/helpers/getDefaultEditor.ts
|
|
1284
1309
|
import { execSync } from "node:child_process";
|
|
1285
|
-
var
|
|
1286
|
-
|
|
1310
|
+
var commandExists = (command) => {
|
|
1311
|
+
try {
|
|
1312
|
+
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
1313
|
+
return true;
|
|
1314
|
+
} catch {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
}, defaultGetDefaultEditorDeps, getDefaultEditor = async (deps = defaultGetDefaultEditorDeps) => {
|
|
1318
|
+
const config = await deps.getHomeConfig();
|
|
1287
1319
|
if (config.editor) {
|
|
1288
1320
|
return config.editor;
|
|
1289
1321
|
}
|
|
@@ -1293,21 +1325,25 @@ var getDefaultEditor = async () => {
|
|
|
1293
1325
|
if (process.env.VISUAL) {
|
|
1294
1326
|
return process.env.VISUAL;
|
|
1295
1327
|
}
|
|
1296
|
-
const platform =
|
|
1328
|
+
const platform = deps.platform;
|
|
1297
1329
|
if (platform === "win32") {
|
|
1298
1330
|
return "notepad";
|
|
1299
1331
|
}
|
|
1300
1332
|
const editors = ["nano", "vim", "vi"];
|
|
1301
1333
|
for (const editor of editors) {
|
|
1302
|
-
|
|
1303
|
-
execSync(`command -v ${editor}`, { stdio: "ignore" });
|
|
1334
|
+
if (deps.commandExists(editor)) {
|
|
1304
1335
|
return editor;
|
|
1305
|
-
}
|
|
1336
|
+
}
|
|
1306
1337
|
}
|
|
1307
1338
|
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
1339
|
};
|
|
1309
1340
|
var init_getDefaultEditor = __esm(() => {
|
|
1310
1341
|
init_homeConfig();
|
|
1342
|
+
defaultGetDefaultEditorDeps = {
|
|
1343
|
+
getHomeConfig,
|
|
1344
|
+
commandExists,
|
|
1345
|
+
platform: process.platform
|
|
1346
|
+
};
|
|
1311
1347
|
});
|
|
1312
1348
|
|
|
1313
1349
|
// src/commands/env/edit.ts
|
|
@@ -2056,7 +2092,7 @@ var init_program = __esm(() => {
|
|
|
2056
2092
|
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
2093
|
auth.command("list").argument("[environment]", "the environment to list access for").description("list keys with access to an environment").action(authListCommand);
|
|
2058
2094
|
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);
|
|
2095
|
+
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((command, args) => devCommand(command, args));
|
|
2060
2096
|
key = program.command("key").description("manage keys");
|
|
2061
2097
|
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
2098
|
key.command("list").description("list all public keys in the project").action(keyListCommand);
|