@captainsafia/burrow 0.1.0 → 1.0.0-preview.0a24dbc

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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright 2025 Safia Abdalla
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
package/README.md CHANGED
@@ -1,15 +1,120 @@
1
1
  # burrow
2
2
 
3
- To install dependencies:
3
+ A platform-agnostic, directory-scoped secrets manager. Store secrets outside your repos, inherit them through directory ancestry.
4
+
5
+ ```
6
+ ~/projects/ # DATABASE_URL, API_KEY defined here
7
+ ├── app-a/ # inherits both secrets
8
+ ├── app-b/ # inherits both, overrides API_KEY
9
+ │ └── tests/ # blocks API_KEY (uses none)
10
+ └── app-c/ # inherits both secrets
11
+ ```
12
+
13
+ ## Installation
14
+
15
+ **Linux/macOS:**
4
16
 
5
17
  ```bash
18
+ curl -fsSL https://safia.rocks/burrow/install.sh | sh
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Set a secret
24
+
25
+ ```bash
26
+ burrow set API_KEY=sk-live-abc123
27
+ burrow set DATABASE_URL=postgres://localhost/mydb --path ~/projects
28
+ ```
29
+
30
+ ### Get a secret
31
+
32
+ ```bash
33
+ burrow get API_KEY --show
34
+ burrow get API_KEY --format json
35
+ ```
36
+
37
+ ### List all secrets
38
+
39
+ ```bash
40
+ burrow list
41
+ burrow list --format json
42
+ ```
43
+
44
+ ### Export to your shell
45
+
46
+ ```bash
47
+ eval "$(burrow export)"
48
+ eval "$(burrow export --format shell)" && npm start
49
+ ```
50
+
51
+ ### Block inheritance
52
+
53
+ ```bash
54
+ burrow unset API_KEY --path ~/projects/app/tests
55
+ ```
56
+
57
+ ## How It Works
58
+
59
+ Secrets are stored in your user profile:
60
+ - **Linux/macOS:** `$XDG_CONFIG_HOME/burrow` or `~/.config/burrow`
61
+ - **Windows:** `%APPDATA%\burrow`
62
+
63
+ When you request secrets for a directory, burrow:
64
+
65
+ 1. Finds all ancestor paths with stored secrets
66
+ 2. Merges them from shallowest to deepest
67
+ 3. Deeper scopes override shallower ones
68
+ 4. Tombstones (from `unset`) block inheritance
69
+
70
+ ## Library Usage
71
+
72
+ Burrow also works as a TypeScript/JavaScript library:
73
+
74
+ ```typescript
75
+ import { BurrowClient } from '@captainsafia/burrow';
76
+
77
+ const client = new BurrowClient();
78
+
79
+ await client.set('API_KEY', 'secret123', { path: '/my/project' });
80
+
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'
84
+
85
+ const allSecrets = await client.list({ cwd: '/my/project' });
86
+ ```
87
+
88
+ ## Contributing
89
+
90
+ ### Prerequisites
91
+
92
+ - [Bun](https://bun.sh) v1.0 or later
93
+
94
+ ### Setup
95
+
96
+ ```bash
97
+ git clone https://github.com/captainsafia/burrow.git
98
+ cd burrow
6
99
  bun install
7
100
  ```
8
101
 
9
- To run:
102
+ ### Development
10
103
 
11
104
  ```bash
12
- bun run index.ts
105
+ # Run tests
106
+ bun test
107
+
108
+ # Type check
109
+ bun run typecheck
110
+
111
+ # Build npm package
112
+ bun run build
113
+
114
+ # Compile binary
115
+ bun run compile
13
116
  ```
14
117
 
15
- This project was created using `bun init` in bun v1.3.4. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
118
+ ## License
119
+
120
+ MIT
package/dist/api.d.ts CHANGED
@@ -6,41 +6,292 @@ export interface ResolvedSecret {
6
6
  sourcePath: string;
7
7
  }
8
8
  export type ExportFormat = "shell" | "dotenv" | "json";
9
+ /**
10
+ * Configuration options for creating a BurrowClient instance.
11
+ */
9
12
  export interface BurrowClientOptions {
13
+ /**
14
+ * Custom directory for storing the secrets database.
15
+ * Defaults to platform-specific user config directory:
16
+ * - Linux/macOS: `$XDG_CONFIG_HOME/burrow` or `~/.config/burrow`
17
+ * - Windows: `%APPDATA%\burrow`
18
+ *
19
+ * Can also be set via the `BURROW_CONFIG_DIR` environment variable.
20
+ */
10
21
  configDir?: string;
22
+ /**
23
+ * Custom filename for the secrets store.
24
+ * Defaults to `store.json`.
25
+ */
11
26
  storeFileName?: string;
27
+ /**
28
+ * Whether to follow symlinks when canonicalizing paths.
29
+ * Defaults to `true`.
30
+ */
12
31
  followSymlinks?: boolean;
13
32
  }
33
+ /**
34
+ * Options for the `set` method.
35
+ */
14
36
  export interface SetOptions {
37
+ /**
38
+ * Directory path to scope the secret to.
39
+ * Defaults to the current working directory.
40
+ */
15
41
  path?: string;
16
42
  }
43
+ /**
44
+ * Options for the `get` method.
45
+ */
17
46
  export interface GetOptions {
47
+ /**
48
+ * Directory to resolve secrets from.
49
+ * Secrets are inherited from ancestor directories.
50
+ * Defaults to the current working directory.
51
+ */
18
52
  cwd?: string;
19
53
  }
54
+ /**
55
+ * Options for the `list` method.
56
+ */
20
57
  export interface ListOptions {
58
+ /**
59
+ * Directory to resolve secrets from.
60
+ * Secrets are inherited from ancestor directories.
61
+ * Defaults to the current working directory.
62
+ */
21
63
  cwd?: string;
22
64
  }
65
+ /**
66
+ * Options for the `block` method.
67
+ */
23
68
  export interface BlockOptions {
69
+ /**
70
+ * Directory path to scope the tombstone to.
71
+ * Defaults to the current working directory.
72
+ */
24
73
  path?: string;
25
74
  }
75
+ /**
76
+ * Options for the `export` method.
77
+ */
26
78
  export interface ExportOptions {
79
+ /**
80
+ * Directory to resolve secrets from.
81
+ * Secrets are inherited from ancestor directories.
82
+ * Defaults to the current working directory.
83
+ */
27
84
  cwd?: string;
85
+ /**
86
+ * Output format for the exported secrets.
87
+ * - `shell`: Exports as `export KEY='value'` statements (default)
88
+ * - `dotenv`: Exports as `KEY="value"` lines
89
+ * - `json`: Exports as a JSON object
90
+ */
28
91
  format?: ExportFormat;
92
+ /**
93
+ * Whether to show actual values (currently unused, reserved for future use).
94
+ */
29
95
  showValues?: boolean;
96
+ /**
97
+ * Whether to include source paths in JSON output.
98
+ * When true, JSON output includes `{ key: { value, sourcePath } }` format.
99
+ * Only applies when format is `json`.
100
+ */
30
101
  includeSources?: boolean;
31
102
  }
103
+ /**
104
+ * Client for managing directory-scoped secrets.
105
+ *
106
+ * Secrets are stored outside your repository in the user's config directory
107
+ * and are scoped to filesystem paths. Child directories automatically inherit
108
+ * secrets from parent directories, with deeper scopes overriding shallower ones.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * import { BurrowClient } from '@captainsafia/burrow';
113
+ *
114
+ * const client = new BurrowClient();
115
+ *
116
+ * // Set a secret scoped to a directory
117
+ * await client.set('API_KEY', 'sk-live-abc123', { path: '/projects/myapp' });
118
+ *
119
+ * // Get a secret (inherits from parent directories)
120
+ * const secret = await client.get('API_KEY', { cwd: '/projects/myapp/src' });
121
+ * console.log(secret?.value); // 'sk-live-abc123'
122
+ *
123
+ * // Export secrets for shell usage
124
+ * const shellExport = await client.export({ format: 'shell' });
125
+ * // Returns: export API_KEY='sk-live-abc123'
126
+ * ```
127
+ */
32
128
  export declare class BurrowClient {
33
129
  private readonly storage;
34
130
  private readonly resolver;
35
131
  private readonly pathOptions;
132
+ /**
133
+ * Creates a new BurrowClient instance.
134
+ *
135
+ * @param options - Configuration options for the client
136
+ */
36
137
  constructor(options?: BurrowClientOptions);
138
+ /**
139
+ * Sets a secret at the specified path scope.
140
+ *
141
+ * The secret will be available to the specified directory and all its
142
+ * subdirectories, unless overridden or blocked at a deeper level.
143
+ *
144
+ * @param key - Environment variable name. Must match `^[A-Z_][A-Z0-9_]*$`
145
+ * @param value - Secret value to store
146
+ * @param options - Set options including target path
147
+ * @throws Error if the key format is invalid
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * // Set at current directory
152
+ * await client.set('DATABASE_URL', 'postgres://localhost/mydb');
153
+ *
154
+ * // Set at specific path
155
+ * await client.set('API_KEY', 'secret', { path: '/projects/myapp' });
156
+ * ```
157
+ */
37
158
  set(key: string, value: string, options?: SetOptions): Promise<void>;
159
+ /**
160
+ * Gets a secret resolved through directory ancestry.
161
+ *
162
+ * Starting from the specified directory (or cwd), walks up the directory
163
+ * tree to find the nearest scope that defines the key. Deeper scopes
164
+ * override shallower ones.
165
+ *
166
+ * @param key - Environment variable name to retrieve
167
+ * @param options - Get options including working directory
168
+ * @returns The resolved secret with its source path, or undefined if not found
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const secret = await client.get('API_KEY', { cwd: '/projects/myapp/src' });
173
+ * if (secret) {
174
+ * console.log(secret.value); // The secret value
175
+ * console.log(secret.sourcePath); // Path where it was defined
176
+ * }
177
+ * ```
178
+ */
38
179
  get(key: string, options?: GetOptions): Promise<ResolvedSecret | undefined>;
180
+ /**
181
+ * Lists all secrets resolved for a directory.
182
+ *
183
+ * Returns all secrets that would be available in the specified directory,
184
+ * including those inherited from parent directories. Each secret includes
185
+ * its source path indicating where it was defined.
186
+ *
187
+ * @param options - List options including working directory
188
+ * @returns Array of resolved secrets sorted by key name
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * const secrets = await client.list({ cwd: '/projects/myapp' });
193
+ * for (const secret of secrets) {
194
+ * console.log(`${secret.key} from ${secret.sourcePath}`);
195
+ * }
196
+ * ```
197
+ */
39
198
  list(options?: ListOptions): Promise<ResolvedSecret[]>;
199
+ /**
200
+ * Blocks a secret from being inherited at the specified path.
201
+ *
202
+ * Creates a "tombstone" that prevents the key from being inherited from
203
+ * parent directories. The block only affects the specified directory and
204
+ * its subdirectories. The secret remains available in parent directories.
205
+ *
206
+ * A blocked key can be re-enabled by calling `set` at the same or deeper path.
207
+ *
208
+ * @param key - Environment variable name to block. Must match `^[A-Z_][A-Z0-9_]*$`
209
+ * @param options - Block options including target path
210
+ * @throws Error if the key format is invalid
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * // Parent has API_KEY defined
215
+ * await client.set('API_KEY', 'prod-key', { path: '/projects' });
216
+ *
217
+ * // Block it in the test directory
218
+ * await client.block('API_KEY', { path: '/projects/myapp/tests' });
219
+ *
220
+ * // Now API_KEY won't resolve in /projects/myapp/tests or below
221
+ * const secret = await client.get('API_KEY', { cwd: '/projects/myapp/tests' });
222
+ * console.log(secret); // undefined
223
+ * ```
224
+ */
40
225
  block(key: string, options?: BlockOptions): Promise<void>;
226
+ /**
227
+ * Exports resolved secrets in various formats.
228
+ *
229
+ * Generates a formatted string of all secrets resolved for the specified
230
+ * directory, suitable for shell evaluation or configuration files.
231
+ *
232
+ * @param options - Export options including format and working directory
233
+ * @returns Formatted string of secrets
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * // Shell format (default) - use with eval
238
+ * const shell = await client.export({ format: 'shell' });
239
+ * // Returns: export API_KEY='value'\nexport DB_URL='...'
240
+ *
241
+ * // Dotenv format - save to .env file
242
+ * const dotenv = await client.export({ format: 'dotenv' });
243
+ * // Returns: API_KEY="value"\nDB_URL="..."
244
+ *
245
+ * // JSON format - for programmatic use
246
+ * const json = await client.export({ format: 'json' });
247
+ * // Returns: { "API_KEY": "value", "DB_URL": "..." }
248
+ *
249
+ * // JSON with source paths
250
+ * const jsonWithSources = await client.export({
251
+ * format: 'json',
252
+ * includeSources: true
253
+ * });
254
+ * // Returns: { "API_KEY": { "value": "...", "sourcePath": "/..." } }
255
+ * ```
256
+ */
41
257
  export(options?: ExportOptions): Promise<string>;
258
+ /**
259
+ * Resolves all secrets for a directory as a Map.
260
+ *
261
+ * Lower-level method that returns the raw resolution result. Useful for
262
+ * programmatic access when you need to iterate over secrets or perform
263
+ * custom processing.
264
+ *
265
+ * @param cwd - Directory to resolve secrets from. Defaults to current working directory.
266
+ * @returns Map of key names to resolved secrets
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const secrets = await client.resolve('/projects/myapp');
271
+ * for (const [key, secret] of secrets) {
272
+ * console.log(`${key}=${secret.value} (from ${secret.sourcePath})`);
273
+ * }
274
+ * ```
275
+ */
42
276
  resolve(cwd?: string): Promise<Map<string, ResolvedSecret>>;
43
277
  }
278
+ /**
279
+ * Creates a new BurrowClient instance.
280
+ *
281
+ * Convenience function equivalent to `new BurrowClient(options)`.
282
+ *
283
+ * @param options - Configuration options for the client
284
+ * @returns A new BurrowClient instance
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * import { createClient } from '@captainsafia/burrow';
289
+ *
290
+ * const client = createClient({
291
+ * configDir: '/custom/config/path'
292
+ * });
293
+ * ```
294
+ */
44
295
  export declare function createClient(options?: BurrowClientOptions): BurrowClient;
45
296
 
46
297
  export {};
package/dist/api.js CHANGED
@@ -7,7 +7,12 @@ import { randomBytes } from "node:crypto";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  var APP_NAME = "burrow";
10
+ var CONFIG_DIR_ENV = "BURROW_CONFIG_DIR";
10
11
  function getConfigDir() {
12
+ const envOverride = process.env[CONFIG_DIR_ENV];
13
+ if (envOverride) {
14
+ return envOverride;
15
+ }
11
16
  const platform = process.platform;
12
17
  if (platform === "win32") {
13
18
  return getWindowsConfigDir();
@@ -170,11 +175,8 @@ function isAncestorOf(ancestorPath, descendantPath) {
170
175
  class Resolver {
171
176
  storage;
172
177
  pathOptions;
173
- constructor(options = {}) {
174
- this.storage = new Storage({
175
- configDir: options.configDir,
176
- storeFileName: options.storeFileName
177
- });
178
+ constructor(options) {
179
+ this.storage = options.storage;
178
180
  this.pathOptions = {
179
181
  followSymlinks: options.followSymlinks
180
182
  };
@@ -306,8 +308,7 @@ class BurrowClient {
306
308
  storeFileName: options.storeFileName
307
309
  });
308
310
  this.resolver = new Resolver({
309
- configDir: options.configDir,
310
- storeFileName: options.storeFileName,
311
+ storage: this.storage,
311
312
  followSymlinks: options.followSymlinks
312
313
  });
313
314
  this.pathOptions = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@captainsafia/burrow",
3
- "version": "0.1.0",
3
+ "version": "1.0.0-preview.0a24dbc",
4
4
  "description": "Platform-agnostic, directory-scoped secrets manager",
5
5
  "type": "module",
6
6
  "main": "dist/api.js",
@@ -49,5 +49,8 @@
49
49
  "direnv",
50
50
  "configuration"
51
51
  ],
52
- "license": "MIT"
53
- }
52
+ "license": "MIT",
53
+ "dependencies": {
54
+ "commander": "^14.0.2"
55
+ }
56
+ }