@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.
Files changed (3) hide show
  1. package/README.md +8 -5
  2. package/dist/cli.js +82 -46
  3. 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 # pick your SSH key, choose a name
13
- dotenc env edit alice # add your personal secrets
14
- dotenc dev npm start # run with your encrypted env
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.2",
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
- console.error(`You do not have access to this environment.
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
- console.error(`${chalk2.red("Error:")} failed to decrypt the data key. Please ensure you have the correct private key.`);
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, configPath, setHomeConfig = async (config) => {
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
- console.error(`No environment provided. Use -e or set DOTENC_ENV to the environment you want to run the command in.
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
- process.exit(1);
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
- console.error(`${chalk7.red("Error:")} ${validation.reason}`);
1092
- process.exit(1);
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
- console.error(error instanceof Error ? error.message : `Unknown error occurred while decrypting the environment ${environment}.`);
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
- console.error(`${chalk7.red("Error:")} All environments failed to load.`);
1110
- process.exit(1);
1120
+ deps.logError(`${chalk7.red("Error:")} All environments failed to load.`);
1121
+ deps.exit(1);
1111
1122
  }
1112
1123
  if (failureCount > 0) {
1113
- console.error(`${chalk7.yellow("Warning:")} ${failureCount} of ${environments.length} environment(s) failed to load.`);
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
- process.exit(code);
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
- console.error(`${chalk8.red("Error:")} could not resolve your identity. Run ${chalk8.gray("dotenc init")} first.`);
1138
- process.exit(1);
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, configPath2, getProjectConfig = async () => {
1171
- if (existsSync5(configPath2)) {
1172
- const config = JSON.parse(await fs8.readFile(configPath2, "utf-8"));
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
- configPath2 = path8.join(process.cwd(), "dotenc.json");
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 getDefaultEditor = async () => {
1286
- const config = await getHomeConfig();
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 = process.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
- try {
1303
- execSync(`command -v ${editor}`, { stdio: "ignore" });
1334
+ if (deps.commandExists(editor)) {
1304
1335
  return editor;
1305
- } catch {}
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotenc/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "🔐 Git-native encrypted environments powered by your SSH keys",
5
5
  "author": "Ivan Filho <i@ivanfilho.com>",
6
6
  "license": "MIT",