envsec 0.1.2 → 0.1.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 CHANGED
@@ -1,15 +1,77 @@
1
- # secenv
1
+ # envsec
2
2
 
3
- To install dependencies:
3
+ Secure environment secrets management for macOS using the native Keychain.
4
+
5
+ ## Features
6
+
7
+ - Store secrets in macOS Keychain (not plain text files)
8
+ - Organize secrets by environment (dev, staging, prod, etc.)
9
+ - Track secret types (string, number, boolean) and metadata via SQLite
10
+ - Search secrets with glob patterns
11
+
12
+ ## Requirements
13
+
14
+ - macOS
15
+ - Node.js >= 18
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install -g envsec
21
+ ```
22
+
23
+ ```bash
24
+ npx envsec
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ All commands require an environment specified with `--env` (or `-e`):
30
+
31
+ ### Add a secret
32
+
33
+ ```bash
34
+ # Store a string
35
+ secenv -e dev add api.key --word "sk-abc123"
36
+
37
+ # Store a number
38
+ secenv -e dev add server.port --digit 3000
39
+
40
+ # Store a boolean
41
+ secenv -e dev add feature.enabled --bool
42
+ ```
43
+
44
+ ### Get a secret
45
+
46
+ ```bash
47
+ secenv -e dev get api.key
48
+ ```
49
+
50
+ ### List all secrets
4
51
 
5
52
  ```bash
6
- bun install
53
+ secenv -e dev list
7
54
  ```
8
55
 
9
- To run:
56
+ ### Search secrets
10
57
 
11
58
  ```bash
12
- bun run index.ts
59
+ secenv -e dev search "api.*"
13
60
  ```
14
61
 
15
- This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
62
+ ### Delete a secret
63
+
64
+ ```bash
65
+ secenv -e dev delete api.key
66
+
67
+ # or use the alias
68
+ secenv -e dev del api.key
69
+ ```
70
+
71
+ ## How it works
72
+
73
+ Secrets are stored in the macOS Keychain using the `security` command-line tool. Metadata (key names, types, timestamps) is kept in a SQLite database at `~/.secenv/store.sqlite`. Keys must contain at least one dot separator (e.g., `service.account`) which maps to the Keychain service/account structure.
74
+
75
+ ## License
76
+
77
+ MIT
@@ -1,15 +1,19 @@
1
1
  import { Effect, Layer } from "effect";
2
- import Database from "better-sqlite3";
3
- import { mkdirSync } from "node:fs";
2
+ import initSqlJs from "sql.js";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { MetadataStore } from "../services/MetadataStore.js";
7
7
  import { MetadataStoreError, SecretNotFoundError } from "../errors.js";
8
- const dbPath = join(homedir(), ".secenv", "store.sqlite");
9
- const initDb = () => {
10
- mkdirSync(join(homedir(), ".secenv"), { recursive: true });
11
- const db = new Database(dbPath);
12
- db.exec(`
8
+ const dbDir = join(homedir(), ".secenv");
9
+ const dbPath = join(dbDir, "store.sqlite");
10
+ const initDb = async () => {
11
+ mkdirSync(dbDir, { recursive: true });
12
+ const SQL = await initSqlJs();
13
+ const db = existsSync(dbPath)
14
+ ? new SQL.Database(readFileSync(dbPath))
15
+ : new SQL.Database();
16
+ db.run(`
13
17
  CREATE TABLE IF NOT EXISTS secrets (
14
18
  id INTEGER PRIMARY KEY AUTOINCREMENT,
15
19
  env TEXT NOT NULL,
@@ -20,10 +24,14 @@ const initDb = () => {
20
24
  UNIQUE(env, key)
21
25
  )
22
26
  `);
27
+ persist(db);
23
28
  return db;
24
29
  };
30
+ const persist = (db) => {
31
+ writeFileSync(dbPath, Buffer.from(db.export()));
32
+ };
25
33
  const make = Effect.gen(function* () {
26
- const db = yield* Effect.try({
34
+ const db = yield* Effect.tryPromise({
27
35
  try: () => initDb(),
28
36
  catch: (error) => new MetadataStoreError({
29
37
  operation: "init",
@@ -34,11 +42,12 @@ const make = Effect.gen(function* () {
34
42
  upsert: Effect.fn("SqliteMetadataStore.upsert")(function* (env, key, type) {
35
43
  yield* Effect.try({
36
44
  try: () => {
37
- db.prepare(`INSERT INTO secrets (env, key, type)
45
+ db.run(`INSERT INTO secrets (env, key, type)
38
46
  VALUES (?, ?, ?)
39
47
  ON CONFLICT(env, key) DO UPDATE SET
40
48
  type = excluded.type,
41
- updated_at = datetime('now')`).run(env, key, type);
49
+ updated_at = datetime('now')`, [env, key, type]);
50
+ persist(db);
42
51
  },
43
52
  catch: (error) => new MetadataStoreError({
44
53
  operation: "upsert",
@@ -48,9 +57,17 @@ const make = Effect.gen(function* () {
48
57
  }),
49
58
  get: Effect.fn("SqliteMetadataStore.get")(function* (env, key) {
50
59
  const row = yield* Effect.try({
51
- try: () => db
52
- .prepare(`SELECT key, type, created_at, updated_at FROM secrets WHERE env = ? AND key = ?`)
53
- .get(env, key),
60
+ try: () => {
61
+ const stmt = db.prepare(`SELECT key, type, created_at, updated_at FROM secrets WHERE env = ? AND key = ?`);
62
+ stmt.bind([env, key]);
63
+ if (!stmt.step()) {
64
+ stmt.free();
65
+ return null;
66
+ }
67
+ const result = stmt.getAsObject();
68
+ stmt.free();
69
+ return result;
70
+ },
54
71
  catch: (error) => new MetadataStoreError({
55
72
  operation: "get",
56
73
  message: `Failed to get metadata for ${env}/${key}: ${error}`,
@@ -68,7 +85,8 @@ const make = Effect.gen(function* () {
68
85
  remove: Effect.fn("SqliteMetadataStore.remove")(function* (env, key) {
69
86
  yield* Effect.try({
70
87
  try: () => {
71
- db.prepare(`DELETE FROM secrets WHERE env = ? AND key = ?`).run(env, key);
88
+ db.run(`DELETE FROM secrets WHERE env = ? AND key = ?`, [env, key]);
89
+ persist(db);
72
90
  },
73
91
  catch: (error) => new MetadataStoreError({
74
92
  operation: "remove",
@@ -78,9 +96,16 @@ const make = Effect.gen(function* () {
78
96
  }),
79
97
  search: Effect.fn("SqliteMetadataStore.search")(function* (env, pattern) {
80
98
  return yield* Effect.try({
81
- try: () => db
82
- .prepare(`SELECT key, type FROM secrets WHERE env = ? AND key GLOB ?`)
83
- .all(env, pattern),
99
+ try: () => {
100
+ const results = [];
101
+ const stmt = db.prepare(`SELECT key, type FROM secrets WHERE env = ? AND key GLOB ?`);
102
+ stmt.bind([env, pattern]);
103
+ while (stmt.step()) {
104
+ results.push(stmt.getAsObject());
105
+ }
106
+ stmt.free();
107
+ return results;
108
+ },
84
109
  catch: (error) => new MetadataStoreError({
85
110
  operation: "search",
86
111
  message: `Failed to search metadata for ${env}/${pattern}: ${error}`,
@@ -89,9 +114,16 @@ const make = Effect.gen(function* () {
89
114
  }),
90
115
  list: Effect.fn("SqliteMetadataStore.list")(function* (env) {
91
116
  return yield* Effect.try({
92
- try: () => db
93
- .prepare(`SELECT key, type, updated_at FROM secrets WHERE env = ? ORDER BY key`)
94
- .all(env),
117
+ try: () => {
118
+ const results = [];
119
+ const stmt = db.prepare(`SELECT key, type, updated_at FROM secrets WHERE env = ? ORDER BY key`);
120
+ stmt.bind([env]);
121
+ while (stmt.step()) {
122
+ results.push(stmt.getAsObject());
123
+ }
124
+ stmt.free();
125
+ return results;
126
+ },
95
127
  catch: (error) => new MetadataStoreError({
96
128
  operation: "list",
97
129
  message: `Failed to list metadata for ${env}: ${error}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envsec",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Secure environment secrets management using macOS Keychain",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,12 +40,12 @@
40
40
  "@effect/cli": "^0.73.2",
41
41
  "@effect/platform": "^0.94.5",
42
42
  "@effect/platform-node": "^0.104.1",
43
- "better-sqlite3": "^12.8.0",
44
- "effect": "^3.19.19"
43
+ "effect": "^3.19.19",
44
+ "sql.js": "^1.14.1"
45
45
  },
46
46
  "devDependencies": {
47
- "@types/better-sqlite3": "^7.6.13",
48
47
  "@types/node": "^22",
48
+ "@types/sql.js": "^1.4.9",
49
49
  "typescript": "^5"
50
50
  }
51
51
  }