@captainsafia/burrow 1.3.0-preview.4437197 → 1.3.0-preview.c020b98
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/dist/api.d.ts +20 -44
- package/dist/api.js +48 -124
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -21,25 +21,11 @@ export interface LoadedSecret {
|
|
|
21
21
|
value: string;
|
|
22
22
|
sourcePath: string;
|
|
23
23
|
}
|
|
24
|
-
export interface HookState {
|
|
25
|
-
/** Currently loaded secret keys and their values */
|
|
26
|
-
secrets: LoadedSecret[];
|
|
27
|
-
/** The last directory that triggered loading */
|
|
28
|
-
lastDir: string;
|
|
29
|
-
/** Timestamp when secrets were loaded */
|
|
30
|
-
loadedAt: string;
|
|
31
|
-
/** Keys that were skipped due to conflicts */
|
|
32
|
-
skippedKeys: string[];
|
|
33
|
-
}
|
|
34
24
|
export interface HookDiff {
|
|
35
25
|
/** Keys to unset from the environment */
|
|
36
|
-
|
|
26
|
+
toUnset: string[];
|
|
37
27
|
/** Secrets to set in the environment */
|
|
38
|
-
|
|
39
|
-
/** Keys that already exist in the environment and were skipped */
|
|
40
|
-
skipped: string[];
|
|
41
|
-
/** Keys that remain unchanged */
|
|
42
|
-
unchanged: string[];
|
|
28
|
+
toSet: LoadedSecret[];
|
|
43
29
|
}
|
|
44
30
|
/**
|
|
45
31
|
* Configuration options for creating a BurrowClient instance.
|
|
@@ -173,6 +159,11 @@ export interface HookOptions {
|
|
|
173
159
|
* Defaults to respecting NO_COLOR environment variable.
|
|
174
160
|
*/
|
|
175
161
|
useColor?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* Keys that were previously loaded by the hook.
|
|
164
|
+
* Used to compute which keys need to be unset.
|
|
165
|
+
*/
|
|
166
|
+
previousKeys?: string[];
|
|
176
167
|
}
|
|
177
168
|
/**
|
|
178
169
|
* Result of the `hook` method.
|
|
@@ -187,9 +178,13 @@ export interface HookResult {
|
|
|
187
178
|
*/
|
|
188
179
|
message?: string;
|
|
189
180
|
/**
|
|
190
|
-
* The
|
|
181
|
+
* The secrets that were loaded.
|
|
182
|
+
*/
|
|
183
|
+
secrets: LoadedSecret[];
|
|
184
|
+
/**
|
|
185
|
+
* The keys that were unloaded.
|
|
191
186
|
*/
|
|
192
|
-
|
|
187
|
+
unloadedKeys: string[];
|
|
193
188
|
/**
|
|
194
189
|
* Whether the directory is trusted.
|
|
195
190
|
*/
|
|
@@ -253,7 +248,6 @@ export declare class BurrowClient {
|
|
|
253
248
|
private readonly resolver;
|
|
254
249
|
private readonly pathOptions;
|
|
255
250
|
private readonly trustManager;
|
|
256
|
-
private readonly hookStateManager;
|
|
257
251
|
/**
|
|
258
252
|
* Creates a new BurrowClient instance.
|
|
259
253
|
*
|
|
@@ -498,41 +492,23 @@ export declare class BurrowClient {
|
|
|
498
492
|
* ```
|
|
499
493
|
*/
|
|
500
494
|
listTrusted(): Promise<TrustedPath[]>;
|
|
501
|
-
/**
|
|
502
|
-
* Gets the current hook state (loaded secrets).
|
|
503
|
-
*
|
|
504
|
-
* @returns The current hook state or undefined if no secrets are loaded
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```typescript
|
|
508
|
-
* const state = await client.getHookState();
|
|
509
|
-
* if (state) {
|
|
510
|
-
* console.log(`${state.secrets.length} secrets loaded from ${state.lastDir}`);
|
|
511
|
-
* }
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
getHookState(): Promise<HookState | undefined>;
|
|
515
|
-
/**
|
|
516
|
-
* Clears the hook state (unloads all secrets tracking).
|
|
517
|
-
*
|
|
518
|
-
* Note: This only clears the state file. The actual environment variables
|
|
519
|
-
* remain set until the shell exits or they are explicitly unset.
|
|
520
|
-
*/
|
|
521
|
-
clearHookState(): Promise<void>;
|
|
522
495
|
/**
|
|
523
496
|
* Processes a directory change for the shell hook.
|
|
524
497
|
*
|
|
525
498
|
* This is the main method called by shell hooks on directory change.
|
|
526
|
-
* It checks trust, resolves secrets, computes the diff
|
|
527
|
-
* the commands needed to update the environment.
|
|
499
|
+
* It checks trust, resolves secrets, computes the diff from previous
|
|
500
|
+
* state, and returns the commands needed to update the environment.
|
|
528
501
|
*
|
|
529
502
|
* @param cwd - The new working directory
|
|
530
|
-
* @param options - Hook options including shell type
|
|
503
|
+
* @param options - Hook options including shell type and previous keys
|
|
531
504
|
* @returns Hook result with commands to execute and optional message
|
|
532
505
|
*
|
|
533
506
|
* @example
|
|
534
507
|
* ```typescript
|
|
535
|
-
* const result = await client.hook('/projects/myapp', {
|
|
508
|
+
* const result = await client.hook('/projects/myapp', {
|
|
509
|
+
* shell: 'bash',
|
|
510
|
+
* previousKeys: ['OLD_KEY'] // Keys from last hook call
|
|
511
|
+
* });
|
|
536
512
|
* if (result.trusted) {
|
|
537
513
|
* for (const cmd of result.commands) {
|
|
538
514
|
* console.log(cmd); // Execute in shell
|
package/dist/api.js
CHANGED
|
@@ -471,107 +471,33 @@ class TrustManager {
|
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
473
|
// src/core/hook.ts
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.configDir = options.configDir ?? getConfigDir();
|
|
483
|
-
this.stateFilePath = join3(this.configDir, HOOK_STATE_FILE);
|
|
484
|
-
}
|
|
485
|
-
async load() {
|
|
486
|
-
try {
|
|
487
|
-
const content = await readFile(this.stateFilePath, "utf-8");
|
|
488
|
-
return JSON.parse(content);
|
|
489
|
-
} catch {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
async save(state) {
|
|
494
|
-
await mkdir2(this.configDir, { recursive: true });
|
|
495
|
-
await writeFile(this.stateFilePath, JSON.stringify(state, null, 2));
|
|
496
|
-
}
|
|
497
|
-
async clear() {
|
|
498
|
-
try {
|
|
499
|
-
await rm(this.stateFilePath, { force: true });
|
|
500
|
-
} catch {}
|
|
501
|
-
}
|
|
502
|
-
computeDiff(currentState, newSecrets, env = process.env) {
|
|
503
|
-
const currentKeys = new Map;
|
|
504
|
-
if (currentState) {
|
|
505
|
-
for (const secret of currentState.secrets) {
|
|
506
|
-
currentKeys.set(secret.key, secret);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
const skippedFromState = new Set(currentState?.skippedKeys ?? []);
|
|
510
|
-
const unset = [];
|
|
511
|
-
const set = [];
|
|
512
|
-
const skipped = [];
|
|
513
|
-
const unchanged = [];
|
|
514
|
-
for (const [key] of currentKeys) {
|
|
515
|
-
if (!newSecrets.has(key)) {
|
|
516
|
-
unset.push(key);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
for (const [key, secret] of newSecrets) {
|
|
520
|
-
const loadedSecret = {
|
|
521
|
-
key: secret.key,
|
|
522
|
-
value: secret.value,
|
|
523
|
-
sourcePath: secret.sourcePath
|
|
524
|
-
};
|
|
525
|
-
const current = currentKeys.get(key);
|
|
526
|
-
if (current) {
|
|
527
|
-
if (current.value === secret.value && current.sourcePath === secret.sourcePath) {
|
|
528
|
-
unchanged.push(key);
|
|
529
|
-
} else {
|
|
530
|
-
set.push(loadedSecret);
|
|
531
|
-
}
|
|
532
|
-
} else {
|
|
533
|
-
const envValue = env[key];
|
|
534
|
-
if (envValue !== undefined && !skippedFromState.has(key)) {
|
|
535
|
-
skipped.push(key);
|
|
536
|
-
} else {
|
|
537
|
-
set.push(loadedSecret);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return { unset, set, skipped, unchanged };
|
|
542
|
-
}
|
|
543
|
-
async applyDiff(currentState, diff, newDir) {
|
|
544
|
-
const secretsMap = new Map;
|
|
545
|
-
if (currentState) {
|
|
546
|
-
for (const secret of currentState.secrets) {
|
|
547
|
-
if (!diff.unset.includes(secret.key)) {
|
|
548
|
-
secretsMap.set(secret.key, secret);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
for (const secret of diff.set) {
|
|
553
|
-
secretsMap.set(secret.key, secret);
|
|
554
|
-
}
|
|
555
|
-
const skippedKeys = [...currentState?.skippedKeys ?? []];
|
|
556
|
-
for (const key of diff.skipped) {
|
|
557
|
-
if (!skippedKeys.includes(key)) {
|
|
558
|
-
skippedKeys.push(key);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
const newState = {
|
|
562
|
-
secrets: Array.from(secretsMap.values()),
|
|
563
|
-
lastDir: newDir,
|
|
564
|
-
loadedAt: new Date().toISOString(),
|
|
565
|
-
skippedKeys
|
|
566
|
-
};
|
|
567
|
-
await this.save(newState);
|
|
568
|
-
return newState;
|
|
474
|
+
function resolveToLoadedSecrets(secrets) {
|
|
475
|
+
const result = [];
|
|
476
|
+
for (const [, secret] of secrets) {
|
|
477
|
+
result.push({
|
|
478
|
+
key: secret.key,
|
|
479
|
+
value: secret.value,
|
|
480
|
+
sourcePath: secret.sourcePath
|
|
481
|
+
});
|
|
569
482
|
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
function computeHookDiff(previousKeys, newSecrets) {
|
|
486
|
+
const newKeys = new Set(newSecrets.map((s) => s.key));
|
|
487
|
+
const toUnset = [];
|
|
488
|
+
for (const key of previousKeys) {
|
|
489
|
+
if (!newKeys.has(key)) {
|
|
490
|
+
toUnset.push(key);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
toUnset,
|
|
495
|
+
toSet: newSecrets
|
|
496
|
+
};
|
|
570
497
|
}
|
|
571
498
|
function formatHookMessage(diff, useColor = true) {
|
|
572
|
-
const loaded = diff.
|
|
573
|
-
const unloaded = diff.
|
|
574
|
-
const skippedCount = diff.skipped.length;
|
|
499
|
+
const loaded = diff.toSet.length;
|
|
500
|
+
const unloaded = diff.toUnset.length;
|
|
575
501
|
if (loaded === 0 && unloaded === 0) {
|
|
576
502
|
return;
|
|
577
503
|
}
|
|
@@ -585,22 +511,19 @@ function formatHookMessage(diff, useColor = true) {
|
|
|
585
511
|
} else {
|
|
586
512
|
message += `unloaded ${unloaded} secret${unloaded === 1 ? "" : "s"}`;
|
|
587
513
|
}
|
|
588
|
-
if (skippedCount > 0) {
|
|
589
|
-
message += ` (${skippedCount} skipped)`;
|
|
590
|
-
}
|
|
591
514
|
message += reset;
|
|
592
515
|
return message;
|
|
593
516
|
}
|
|
594
517
|
function generateShellCommands(diff, shell) {
|
|
595
518
|
const commands = [];
|
|
596
|
-
for (const key of diff.
|
|
519
|
+
for (const key of diff.toUnset) {
|
|
597
520
|
if (shell === "fish") {
|
|
598
521
|
commands.push(`set -e ${key}`);
|
|
599
522
|
} else {
|
|
600
523
|
commands.push(`unset ${key}`);
|
|
601
524
|
}
|
|
602
525
|
}
|
|
603
|
-
for (const secret of diff.
|
|
526
|
+
for (const secret of diff.toSet) {
|
|
604
527
|
const escapedValue = escapeShellValue2(secret.value, shell);
|
|
605
528
|
if (shell === "fish") {
|
|
606
529
|
commands.push(`set -gx ${secret.key} ${escapedValue}`);
|
|
@@ -624,7 +547,6 @@ class BurrowClient {
|
|
|
624
547
|
resolver;
|
|
625
548
|
pathOptions;
|
|
626
549
|
trustManager;
|
|
627
|
-
hookStateManager;
|
|
628
550
|
constructor(options = {}) {
|
|
629
551
|
this.storage = new Storage({
|
|
630
552
|
configDir: options.configDir,
|
|
@@ -641,9 +563,6 @@ class BurrowClient {
|
|
|
641
563
|
storage: this.storage,
|
|
642
564
|
followSymlinks: options.followSymlinks
|
|
643
565
|
});
|
|
644
|
-
this.hookStateManager = new HookStateManager({
|
|
645
|
-
configDir: options.configDir
|
|
646
|
-
});
|
|
647
566
|
}
|
|
648
567
|
async set(key, value, options = {}) {
|
|
649
568
|
assertValidEnvKey(key);
|
|
@@ -694,43 +613,48 @@ class BurrowClient {
|
|
|
694
613
|
async listTrusted() {
|
|
695
614
|
return this.trustManager.list();
|
|
696
615
|
}
|
|
697
|
-
async getHookState() {
|
|
698
|
-
return this.hookStateManager.load();
|
|
699
|
-
}
|
|
700
|
-
async clearHookState() {
|
|
701
|
-
return this.hookStateManager.clear();
|
|
702
|
-
}
|
|
703
616
|
async hook(cwd, options) {
|
|
617
|
+
const previousKeys = new Set(options.previousKeys ?? []);
|
|
704
618
|
if (process.env["BURROW_AUTOLOAD"] === "0") {
|
|
705
|
-
const
|
|
619
|
+
const diff2 = computeHookDiff(previousKeys, []);
|
|
620
|
+
const commands2 = generateShellCommands(diff2, options.shell);
|
|
621
|
+
const useColor2 = options.useColor ?? !process.env["NO_COLOR"];
|
|
622
|
+
const message2 = diff2.toUnset.length > 0 ? formatHookMessage(diff2, useColor2) : undefined;
|
|
706
623
|
return {
|
|
707
|
-
commands:
|
|
708
|
-
|
|
624
|
+
commands: commands2,
|
|
625
|
+
message: message2,
|
|
626
|
+
secrets: [],
|
|
627
|
+
unloadedKeys: diff2.toUnset,
|
|
709
628
|
trusted: false,
|
|
710
629
|
notTrustedReason: "autoload-disabled"
|
|
711
630
|
};
|
|
712
631
|
}
|
|
713
632
|
const trustResult = await this.isTrusted({ path: cwd });
|
|
714
633
|
if (!trustResult.trusted) {
|
|
715
|
-
const
|
|
634
|
+
const diff2 = computeHookDiff(previousKeys, []);
|
|
635
|
+
const commands2 = generateShellCommands(diff2, options.shell);
|
|
636
|
+
const useColor2 = options.useColor ?? !process.env["NO_COLOR"];
|
|
637
|
+
const message2 = diff2.toUnset.length > 0 ? formatHookMessage(diff2, useColor2) : undefined;
|
|
716
638
|
return {
|
|
717
|
-
commands:
|
|
718
|
-
|
|
639
|
+
commands: commands2,
|
|
640
|
+
message: message2,
|
|
641
|
+
secrets: [],
|
|
642
|
+
unloadedKeys: diff2.toUnset,
|
|
719
643
|
trusted: false,
|
|
720
644
|
notTrustedReason: trustResult.reason
|
|
721
645
|
};
|
|
722
646
|
}
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
const diff =
|
|
647
|
+
const resolvedSecrets = await this.resolve(cwd);
|
|
648
|
+
const secrets = resolveToLoadedSecrets(resolvedSecrets);
|
|
649
|
+
const diff = computeHookDiff(previousKeys, secrets);
|
|
726
650
|
const commands = generateShellCommands(diff, options.shell);
|
|
727
|
-
await this.hookStateManager.applyDiff(currentState, diff, cwd);
|
|
728
651
|
const useColor = options.useColor ?? !process.env["NO_COLOR"];
|
|
729
652
|
const message = formatHookMessage(diff, useColor);
|
|
730
653
|
return {
|
|
731
654
|
commands,
|
|
732
655
|
message,
|
|
733
|
-
|
|
656
|
+
secrets,
|
|
657
|
+
unloadedKeys: diff.toUnset,
|
|
734
658
|
trusted: true
|
|
735
659
|
};
|
|
736
660
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@captainsafia/burrow",
|
|
3
|
-
"version": "1.3.0-preview.
|
|
3
|
+
"version": "1.3.0-preview.c020b98",
|
|
4
4
|
"description": "Platform-agnostic, directory-scoped secrets manager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/api.js",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"@inquirer/password": "^5.0.3",
|
|
55
55
|
"clipboardy": "^5.0.2",
|
|
56
56
|
"commander": "^14.0.2",
|
|
57
|
-
"gh-release-update-notifier": "^1.0.
|
|
57
|
+
"gh-release-update-notifier": "^1.0.1"
|
|
58
58
|
}
|
|
59
59
|
}
|