envsec 0.1.4 → 0.1.5
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 +27 -3
- package/dist/cli/env-file.js +22 -0
- package/dist/cli/run.js +26 -0
- package/dist/main.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# secenv
|
|
2
2
|
|
|
3
3
|
Secure environment secrets management for macOS using the native Keychain.
|
|
4
4
|
|
|
@@ -8,6 +8,8 @@ Secure environment secrets management for macOS using the native Keychain.
|
|
|
8
8
|
- Organize secrets by environment (dev, staging, prod, etc.)
|
|
9
9
|
- Track secret types (string, number, boolean) and metadata via SQLite
|
|
10
10
|
- Search secrets with glob patterns
|
|
11
|
+
- Run commands with secret interpolation
|
|
12
|
+
- Export secrets to `.env` files
|
|
11
13
|
|
|
12
14
|
## Requirements
|
|
13
15
|
|
|
@@ -17,11 +19,11 @@ Secure environment secrets management for macOS using the native Keychain.
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
npm install -g
|
|
22
|
+
npm install -g secenv
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
```bash
|
|
24
|
-
npx
|
|
26
|
+
npx secenv
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
## Usage
|
|
@@ -59,6 +61,28 @@ secenv -e dev list
|
|
|
59
61
|
secenv -e dev search "api.*"
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
### Generate a .env file
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Creates .env with all secrets from the environment
|
|
68
|
+
secenv -e dev env-file
|
|
69
|
+
|
|
70
|
+
# Specify a custom output path
|
|
71
|
+
secenv -e dev env-file --output .env.local
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Keys are converted to `UPPER_SNAKE_CASE` (e.g. `api.token` → `API_TOKEN`).
|
|
75
|
+
|
|
76
|
+
### Run a command with secrets
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Placeholders {key} are resolved with secret values before execution
|
|
80
|
+
secenv -e dev run 'curl {api.url} -H "Authorization: Bearer {api.token}"'
|
|
81
|
+
|
|
82
|
+
# Any {dotted.key} in the command string is replaced with its value
|
|
83
|
+
secenv -e prod run 'psql {db.connection_string}'
|
|
84
|
+
```
|
|
85
|
+
|
|
62
86
|
### Delete a secret
|
|
63
87
|
|
|
64
88
|
```bash
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command, Options } from "@effect/cli";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { SecretStore } from "../services/SecretStore.js";
|
|
4
|
+
import { rootCommand } from "./root.js";
|
|
5
|
+
import { writeFileSync } from "node:fs";
|
|
6
|
+
const output = Options.text("output").pipe(Options.withAlias("o"), Options.withDescription("Output file path (default: .env)"), Options.withDefault(".env"));
|
|
7
|
+
export const envFileCommand = Command.make("env-file", { output }, ({ output }) => Effect.gen(function* () {
|
|
8
|
+
const { env } = yield* rootCommand;
|
|
9
|
+
const secrets = yield* SecretStore.list(env);
|
|
10
|
+
if (secrets.length === 0) {
|
|
11
|
+
yield* Effect.log(`No secrets found for env "${env}"`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const lines = [];
|
|
15
|
+
for (const item of secrets) {
|
|
16
|
+
const value = yield* SecretStore.get(env, item.key);
|
|
17
|
+
const envKey = item.key.toUpperCase().replaceAll(".", "_");
|
|
18
|
+
lines.push(`${envKey}=${String(value)}`);
|
|
19
|
+
}
|
|
20
|
+
writeFileSync(output, lines.join("\n") + "\n", "utf-8");
|
|
21
|
+
yield* Effect.log(`Written ${secrets.length} secret(s) to ${output}`);
|
|
22
|
+
}));
|
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command, Args } from "@effect/cli";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { SecretStore } from "../services/SecretStore.js";
|
|
4
|
+
import { rootCommand } from "./root.js";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
const cmd = Args.text({ name: "command" }).pipe(Args.withDescription("Command to execute. Use {key} placeholders for secret interpolation"));
|
|
7
|
+
export const runCommand = Command.make("run", { cmd }, ({ cmd }) => Effect.gen(function* () {
|
|
8
|
+
const { env } = yield* rootCommand;
|
|
9
|
+
// Find all {key} placeholders
|
|
10
|
+
const placeholders = [...cmd.matchAll(/\{([^}]+)\}/g)];
|
|
11
|
+
let resolved = cmd;
|
|
12
|
+
for (const match of placeholders) {
|
|
13
|
+
const key = match[1];
|
|
14
|
+
const value = yield* SecretStore.get(env, key);
|
|
15
|
+
resolved = resolved.replaceAll(`{${key}}`, String(value));
|
|
16
|
+
}
|
|
17
|
+
if (placeholders.length > 0) {
|
|
18
|
+
yield* Effect.log(`Resolved ${placeholders.length} secret(s)`);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
execSync(resolved, { stdio: "inherit", shell: "/bin/bash" });
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
yield* Effect.fail(new Error(`Command exited with code ${e.status ?? 1}`));
|
|
25
|
+
}
|
|
26
|
+
}));
|
package/dist/main.js
CHANGED
|
@@ -9,10 +9,12 @@ import { getCommand } from "./cli/read.js";
|
|
|
9
9
|
import { deleteCommand, delCommand } from "./cli/delete.js";
|
|
10
10
|
import { searchCommand } from "./cli/search.js";
|
|
11
11
|
import { listCommand } from "./cli/list.js";
|
|
12
|
+
import { runCommand } from "./cli/run.js";
|
|
13
|
+
import { envFileCommand } from "./cli/env-file.js";
|
|
12
14
|
import { SecretStore } from "./services/SecretStore.js";
|
|
13
15
|
const require = createRequire(import.meta.url);
|
|
14
16
|
const pkg = require("../package.json");
|
|
15
|
-
const command = rootCommand.pipe(Command.withSubcommands([addCommand, getCommand, deleteCommand, delCommand, searchCommand, listCommand]));
|
|
17
|
+
const command = rootCommand.pipe(Command.withSubcommands([addCommand, getCommand, deleteCommand, delCommand, searchCommand, listCommand, runCommand, envFileCommand]));
|
|
16
18
|
const cli = Command.run(command, {
|
|
17
19
|
name: "secenv",
|
|
18
20
|
version: pkg.version,
|