@captainsafia/burrow 1.0.0-preview.6e67e96 → 1.0.0-preview.aac83a8
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 -6
- package/dist/api.d.ts +70 -7
- package/dist/api.js +35 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ burrow set DATABASE_URL=postgres://localhost/mydb --path ~/projects
|
|
|
30
30
|
### Get a secret
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
burrow get API_KEY
|
|
33
|
+
burrow get API_KEY
|
|
34
34
|
burrow get API_KEY --format json
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -54,6 +54,14 @@ eval "$(burrow export --format shell)" && npm start
|
|
|
54
54
|
burrow unset API_KEY --path ~/projects/app/tests
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
### Remove a secret
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
burrow remove API_KEY --path ~/projects/app
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Unlike `unset` which blocks inheritance, `remove` deletes the entry entirely, restoring inheritance from parent directories.
|
|
64
|
+
|
|
57
65
|
## How It Works
|
|
58
66
|
|
|
59
67
|
Secrets are stored in your user profile:
|
|
@@ -76,13 +84,26 @@ import { BurrowClient } from '@captainsafia/burrow';
|
|
|
76
84
|
|
|
77
85
|
const client = new BurrowClient();
|
|
78
86
|
|
|
79
|
-
|
|
87
|
+
try {
|
|
88
|
+
await client.set('API_KEY', 'secret123', { path: '/my/project' });
|
|
80
89
|
|
|
81
|
-
const secret = await client.get('API_KEY', { cwd: '/my/project/subdir' });
|
|
82
|
-
console.log(secret?.value); // 'secret123'
|
|
83
|
-
console.log(secret?.sourcePath); // '/my/project'
|
|
90
|
+
const secret = await client.get('API_KEY', { cwd: '/my/project/subdir' });
|
|
91
|
+
console.log(secret?.value); // 'secret123'
|
|
92
|
+
console.log(secret?.sourcePath); // '/my/project'
|
|
84
93
|
|
|
85
|
-
const allSecrets = await client.list({ cwd: '/my/project' });
|
|
94
|
+
const allSecrets = await client.list({ cwd: '/my/project' });
|
|
95
|
+
} finally {
|
|
96
|
+
client.close(); // Clean up database connection
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or with TypeScript's `using` declarations for automatic cleanup:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
{
|
|
104
|
+
using client = new BurrowClient();
|
|
105
|
+
await client.set('API_KEY', 'secret123');
|
|
106
|
+
} // Automatically cleaned up
|
|
86
107
|
```
|
|
87
108
|
|
|
88
109
|
## Contributing
|
package/dist/api.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface BurrowClientOptions {
|
|
|
21
21
|
configDir?: string;
|
|
22
22
|
/**
|
|
23
23
|
* Custom filename for the secrets store.
|
|
24
|
-
* Defaults to `store.
|
|
24
|
+
* Defaults to `store.db`.
|
|
25
25
|
*/
|
|
26
26
|
storeFileName?: string;
|
|
27
27
|
/**
|
|
@@ -72,6 +72,16 @@ export interface BlockOptions {
|
|
|
72
72
|
*/
|
|
73
73
|
path?: string;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Options for the `remove` method.
|
|
77
|
+
*/
|
|
78
|
+
export interface RemoveOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Directory path to remove the secret from.
|
|
81
|
+
* Defaults to the current working directory.
|
|
82
|
+
*/
|
|
83
|
+
path?: string;
|
|
84
|
+
}
|
|
75
85
|
/**
|
|
76
86
|
* Options for the `export` method.
|
|
77
87
|
*/
|
|
@@ -89,10 +99,6 @@ export interface ExportOptions {
|
|
|
89
99
|
* - `json`: Exports as a JSON object
|
|
90
100
|
*/
|
|
91
101
|
format?: ExportFormat;
|
|
92
|
-
/**
|
|
93
|
-
* Whether to show actual values (currently unused, reserved for future use).
|
|
94
|
-
*/
|
|
95
|
-
showValues?: boolean;
|
|
96
102
|
/**
|
|
97
103
|
* Whether to include source paths in JSON output.
|
|
98
104
|
* When true, JSON output includes `{ key: { value, sourcePath } }` format.
|
|
@@ -141,7 +147,7 @@ export declare class BurrowClient {
|
|
|
141
147
|
* The secret will be available to the specified directory and all its
|
|
142
148
|
* subdirectories, unless overridden or blocked at a deeper level.
|
|
143
149
|
*
|
|
144
|
-
* @param key - Environment variable name. Must match `^[A-
|
|
150
|
+
* @param key - Environment variable name. Must match `^[A-Za-z_][A-Za-z0-9_]*$`
|
|
145
151
|
* @param value - Secret value to store
|
|
146
152
|
* @param options - Set options including target path
|
|
147
153
|
* @throws Error if the key format is invalid
|
|
@@ -205,7 +211,7 @@ export declare class BurrowClient {
|
|
|
205
211
|
*
|
|
206
212
|
* A blocked key can be re-enabled by calling `set` at the same or deeper path.
|
|
207
213
|
*
|
|
208
|
-
* @param key - Environment variable name to block. Must match `^[A-
|
|
214
|
+
* @param key - Environment variable name to block. Must match `^[A-Za-z_][A-Za-z0-9_]*$`
|
|
209
215
|
* @param options - Block options including target path
|
|
210
216
|
* @throws Error if the key format is invalid
|
|
211
217
|
*
|
|
@@ -223,6 +229,33 @@ export declare class BurrowClient {
|
|
|
223
229
|
* ```
|
|
224
230
|
*/
|
|
225
231
|
block(key: string, options?: BlockOptions): Promise<void>;
|
|
232
|
+
/**
|
|
233
|
+
* Removes a secret entry entirely from the specified path.
|
|
234
|
+
*
|
|
235
|
+
* Unlike `block`, which creates a tombstone to prevent inheritance,
|
|
236
|
+
* `remove` completely deletes the secret entry. After removal, the key
|
|
237
|
+
* may still be inherited from parent directories if defined there.
|
|
238
|
+
*
|
|
239
|
+
* @param key - Environment variable name to remove. Must match `^[A-Za-z_][A-Za-z0-9_]*$`
|
|
240
|
+
* @param options - Remove options including target path
|
|
241
|
+
* @returns true if the secret was found and removed, false if it didn't exist
|
|
242
|
+
* @throws Error if the key format is invalid
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // Set a secret
|
|
247
|
+
* await client.set('API_KEY', 'secret', { path: '/projects/myapp' });
|
|
248
|
+
*
|
|
249
|
+
* // Remove it entirely
|
|
250
|
+
* const removed = await client.remove('API_KEY', { path: '/projects/myapp' });
|
|
251
|
+
* console.log(removed); // true
|
|
252
|
+
*
|
|
253
|
+
* // Trying to remove again returns false
|
|
254
|
+
* const removedAgain = await client.remove('API_KEY', { path: '/projects/myapp' });
|
|
255
|
+
* console.log(removedAgain); // false
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
remove(key: string, options?: RemoveOptions): Promise<boolean>;
|
|
226
259
|
/**
|
|
227
260
|
* Exports resolved secrets in various formats.
|
|
228
261
|
*
|
|
@@ -274,6 +307,36 @@ export declare class BurrowClient {
|
|
|
274
307
|
* ```
|
|
275
308
|
*/
|
|
276
309
|
resolve(cwd?: string): Promise<Map<string, ResolvedSecret>>;
|
|
310
|
+
/**
|
|
311
|
+
* Closes the database connection and releases resources.
|
|
312
|
+
* After calling this method, the client instance should not be used.
|
|
313
|
+
*
|
|
314
|
+
* This method is safe to call multiple times.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* const client = new BurrowClient();
|
|
319
|
+
* try {
|
|
320
|
+
* await client.set('API_KEY', 'value');
|
|
321
|
+
* // ... do work
|
|
322
|
+
* } finally {
|
|
323
|
+
* client.close();
|
|
324
|
+
* }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
close(): void;
|
|
328
|
+
/**
|
|
329
|
+
* Allows using the BurrowClient with `using` declarations for automatic cleanup.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* {
|
|
334
|
+
* using client = new BurrowClient();
|
|
335
|
+
* await client.set('API_KEY', 'value');
|
|
336
|
+
* } // client.close() is called automatically
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
[Symbol.dispose](): void;
|
|
277
340
|
}
|
|
278
341
|
/**
|
|
279
342
|
* Creates a new BurrowClient instance.
|
package/dist/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/storage/index.ts
|
|
2
2
|
import { Database } from "bun:sqlite";
|
|
3
|
-
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { chmod, mkdir } from "node:fs/promises";
|
|
4
4
|
import { join as join2 } from "node:path";
|
|
5
5
|
|
|
6
6
|
// src/platform/index.ts
|
|
@@ -62,8 +62,14 @@ class Storage {
|
|
|
62
62
|
return this.db;
|
|
63
63
|
}
|
|
64
64
|
await mkdir(this.configDir, { recursive: true });
|
|
65
|
+
if (!isWindows()) {
|
|
66
|
+
await chmod(this.configDir, 448);
|
|
67
|
+
}
|
|
65
68
|
this.db = new Database(this.storePath);
|
|
66
69
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
70
|
+
if (!isWindows()) {
|
|
71
|
+
await chmod(this.storePath, 384);
|
|
72
|
+
}
|
|
67
73
|
this.db.run(`
|
|
68
74
|
CREATE TABLE IF NOT EXISTS secrets (
|
|
69
75
|
path TEXT NOT NULL,
|
|
@@ -114,6 +120,11 @@ class Storage {
|
|
|
114
120
|
const rows = db.query("SELECT DISTINCT path FROM secrets").all();
|
|
115
121
|
return rows.map((row) => row.path);
|
|
116
122
|
}
|
|
123
|
+
async getAncestorPaths(canonicalPath) {
|
|
124
|
+
const db = await this.ensureDb();
|
|
125
|
+
const rows = db.query("SELECT DISTINCT path FROM secrets WHERE ? = path OR ? LIKE path || '/' || '%' OR path = '/'").all(canonicalPath, canonicalPath);
|
|
126
|
+
return rows.map((row) => row.path);
|
|
127
|
+
}
|
|
117
128
|
async removeKey(canonicalPath, key) {
|
|
118
129
|
const db = await this.ensureDb();
|
|
119
130
|
const existing = db.query("SELECT path FROM secrets WHERE path = ? AND key = ?").get(canonicalPath, key);
|
|
@@ -123,6 +134,15 @@ class Storage {
|
|
|
123
134
|
db.query("DELETE FROM secrets WHERE path = ? AND key = ?").run(canonicalPath, key);
|
|
124
135
|
return true;
|
|
125
136
|
}
|
|
137
|
+
close() {
|
|
138
|
+
if (this.db) {
|
|
139
|
+
this.db.close();
|
|
140
|
+
this.db = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
[Symbol.dispose]() {
|
|
144
|
+
this.close();
|
|
145
|
+
}
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
// src/core/path.ts
|
|
@@ -159,16 +179,6 @@ function normalizePath(path) {
|
|
|
159
179
|
}
|
|
160
180
|
return normalized;
|
|
161
181
|
}
|
|
162
|
-
function isAncestorOf(ancestorPath, descendantPath) {
|
|
163
|
-
if (ancestorPath === descendantPath) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
const ancestorWithSep = ancestorPath.endsWith(sep) ? ancestorPath : ancestorPath + sep;
|
|
167
|
-
if (isWindows()) {
|
|
168
|
-
return descendantPath.toLowerCase().startsWith(ancestorWithSep.toLowerCase());
|
|
169
|
-
}
|
|
170
|
-
return descendantPath.startsWith(ancestorWithSep);
|
|
171
|
-
}
|
|
172
182
|
// src/core/resolver.ts
|
|
173
183
|
class Resolver {
|
|
174
184
|
storage;
|
|
@@ -182,8 +192,7 @@ class Resolver {
|
|
|
182
192
|
async resolve(cwd) {
|
|
183
193
|
const workingDir = cwd ?? process.cwd();
|
|
184
194
|
const canonicalCwd = await canonicalize(workingDir, this.pathOptions);
|
|
185
|
-
const
|
|
186
|
-
const ancestorPaths = allPaths.filter((storedPath) => isAncestorOf(storedPath, canonicalCwd));
|
|
195
|
+
const ancestorPaths = await this.storage.getAncestorPaths(canonicalCwd);
|
|
187
196
|
ancestorPaths.sort((a, b) => {
|
|
188
197
|
if (isWindows()) {
|
|
189
198
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
@@ -222,7 +231,7 @@ class Resolver {
|
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
233
|
// src/core/formatter.ts
|
|
225
|
-
var ENV_KEY_PATTERN = /^[A-
|
|
234
|
+
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
226
235
|
function validateEnvKey(key) {
|
|
227
236
|
return ENV_KEY_PATTERN.test(key);
|
|
228
237
|
}
|
|
@@ -331,6 +340,12 @@ class BurrowClient {
|
|
|
331
340
|
const canonicalPath = await canonicalize(targetPath, this.pathOptions);
|
|
332
341
|
await this.storage.setSecret(canonicalPath, key, null);
|
|
333
342
|
}
|
|
343
|
+
async remove(key, options = {}) {
|
|
344
|
+
assertValidEnvKey(key);
|
|
345
|
+
const targetPath = options.path ?? process.cwd();
|
|
346
|
+
const canonicalPath = await canonicalize(targetPath, this.pathOptions);
|
|
347
|
+
return this.storage.removeKey(canonicalPath, key);
|
|
348
|
+
}
|
|
334
349
|
async export(options = {}) {
|
|
335
350
|
const secrets = await this.resolver.resolve(options.cwd);
|
|
336
351
|
const fmt = options.format ?? "shell";
|
|
@@ -341,6 +356,12 @@ class BurrowClient {
|
|
|
341
356
|
async resolve(cwd) {
|
|
342
357
|
return this.resolver.resolve(cwd);
|
|
343
358
|
}
|
|
359
|
+
close() {
|
|
360
|
+
this.storage.close();
|
|
361
|
+
}
|
|
362
|
+
[Symbol.dispose]() {
|
|
363
|
+
this.close();
|
|
364
|
+
}
|
|
344
365
|
}
|
|
345
366
|
function createClient(options) {
|
|
346
367
|
return new BurrowClient(options);
|