Skip to content

Commit f35b298

Browse files
jszobodyclaudehappy-otter
committed
Refactor: Replace service classes with elegant Noun::verb() patterns
Major architectural improvements following Laravel/Taylor Otwell philosophy: - Replaced SecretLoader with SecretCollection::loadFromVaults() - Replaced DiffService with SecretCollection::compare() - Replaced VaultPermissionTester with AbstractVault::testPermissions() - Replaced EnvironmentBuilder with SecretCollection::toEnvironment() and Template::toEnvironment() - Replaced VaultDiscovery with Template::allReferencedVaults() - Removed SecretKeyValidator (validation now in Secret class) Benefits: - More intuitive API (e.g., SecretCollection::loadFromVaults() vs new SecretLoader()->loadFromVaults()) - Reduced class count while maintaining functionality - Better encapsulation - behavior lives with data - Cleaner, more Laravel-like code structure All tests passing (652 passed, only 1 pre-existing failure unrelated to refactoring) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
1 parent 6fb9419 commit f35b298

26 files changed

+502
-663
lines changed

src/Commands/Concerns/ConfiguresVaults.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace STS\Keep\Commands\Concerns;
44

5+
use Exception;
6+
use STS\Keep\Data\Collections\PermissionsCollection;
57
use STS\Keep\Data\VaultConfig;
8+
use STS\Keep\Data\VaultStagePermissions;
69
use STS\Keep\Facades\Keep;
7-
use STS\Keep\Services\VaultPermissionTester;
10+
use STS\Keep\Services\LocalStorage;
811

912
use function Laravel\Prompts\error;
1013
use function Laravel\Prompts\info;
@@ -88,8 +91,7 @@ protected function configureNewVault(): ?array
8891

8992
// Run verify to check and cache permissions for all stages
9093
info("\nVerifying vault permissions...");
91-
$tester = new VaultPermissionTester();
92-
$collection = $tester->testVaultAcrossStages($slug);
94+
$collection = $this->testVaultAcrossStages($slug);
9395

9496
// Display summary of permissions
9597
foreach ($collection->groupByStage() as $stage => $permissions) {
@@ -100,6 +102,32 @@ protected function configureNewVault(): ?array
100102

101103
return ['slug' => $slug, 'config' => $vaultConfig];
102104
}
105+
106+
protected function testVaultAcrossStages(string $vaultName): PermissionsCollection
107+
{
108+
$stages = Keep::getStages();
109+
$collection = new PermissionsCollection();
110+
$localStorage = new LocalStorage();
111+
$vaultPermissions = [];
112+
113+
foreach ($stages as $stage) {
114+
try {
115+
$vault = Keep::vault($vaultName, $stage);
116+
$results = $vault->testPermissions();
117+
$permission = VaultStagePermissions::fromTestResults($vaultName, $stage, $results);
118+
} catch (Exception $e) {
119+
$permission = VaultStagePermissions::fromError($vaultName, $stage, $e->getMessage());
120+
}
121+
122+
$collection->addPermission($permission);
123+
$vaultPermissions[$stage] = $permission->permissions();
124+
}
125+
126+
// Persist permissions for this vault
127+
$localStorage->saveVaultPermissions($vaultName, $vaultPermissions);
128+
129+
return $collection;
130+
}
103131

104132
private function generateUniqueSlug(string $driver): string
105133
{

src/Commands/DiffCommand.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
use Illuminate\Support\Collection;
66
use STS\Keep\Commands\Concerns\GathersInput;
7+
use STS\Keep\Data\Collections\SecretCollection;
78
use STS\Keep\Data\SecretDiff;
89
use STS\Keep\Facades\Keep;
9-
use STS\Keep\Services\DiffService;
1010

1111
use function Laravel\Prompts\spin;
1212
use function Laravel\Prompts\table;
@@ -41,11 +41,10 @@ public function process()
4141
return self::FAILURE;
4242
}
4343

44-
$diffService = new DiffService;
45-
$diffs = spin(fn () => $diffService->compare($vaults, $stages, $this->option('only'), $this->option('except')), 'Gathering secrets for comparison...');
44+
$diffs = spin(fn () => SecretCollection::compare($vaults, $stages, $this->option('only'), $this->option('except')), 'Gathering secrets for comparison...');
4645

4746
if ($diffs->isNotEmpty()) {
48-
$this->displayTable($diffs, $vaults, $stages, $diffService);
47+
$this->displayTable($diffs, $vaults, $stages);
4948
} else {
5049
$this->info('No secrets found in any of the specified vault/stage combinations.');
5150
}
@@ -92,7 +91,7 @@ protected function getStagesToCompare(): array
9291
return Keep::getStages();
9392
}
9493

95-
protected function displayTable(Collection $diffs, array $vaults, array $stages, DiffService $diffService): void
94+
protected function displayTable(Collection $diffs, array $vaults, array $stages): void
9695
{
9796
$this->newLine();
9897
$this->info('Secret Comparison Matrix');
@@ -128,12 +127,12 @@ protected function displayTable(Collection $diffs, array $vaults, array $stages,
128127

129128
table($headers, $rows);
130129

131-
$this->displaySummary($diffs, $vaults, $stages, $diffService);
130+
$this->displaySummary($diffs, $vaults, $stages);
132131
}
133132

134-
protected function displaySummary(Collection $diffs, array $vaults, array $stages, DiffService $diffService): void
133+
protected function displaySummary(Collection $diffs, array $vaults, array $stages): void
135134
{
136-
$summary = $diffService->generateSummary($diffs, $vaults, $stages);
135+
$summary = SecretCollection::generateDiffSummary($diffs, $vaults, $stages);
137136

138137
$this->info('Summary:');
139138
$this->line("• Total secrets: {$summary['total_secrets']}");

src/Commands/ExportCommand.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
use STS\Keep\Services\Export\TemplatePreserveService;
1111
// use STS\Keep\Services\Export\CacheExportService; // Future enhancement
1212
use STS\Keep\Services\OutputWriter;
13-
use STS\Keep\Services\SecretLoader;
14-
use STS\Keep\Services\VaultDiscovery;
1513

1614
class ExportCommand extends BaseCommand
1715
{
@@ -47,13 +45,11 @@ public function __construct(Filesystem $filesystem)
4745
parent::__construct($filesystem);
4846

4947
// Initialize services
50-
$secretLoader = new SecretLoader;
51-
$vaultDiscovery = new VaultDiscovery;
5248
$outputWriter = new OutputWriter($filesystem);
5349

54-
$this->directExport = new DirectExportService($secretLoader, $outputWriter);
55-
$this->templatePreserve = new TemplatePreserveService($secretLoader, $vaultDiscovery, $outputWriter);
56-
$this->templateParse = new TemplateParseService($secretLoader, $vaultDiscovery, $outputWriter);
50+
$this->directExport = new DirectExportService($outputWriter);
51+
$this->templatePreserve = new TemplatePreserveService($outputWriter);
52+
$this->templateParse = new TemplateParseService($outputWriter);
5753
// $this->cacheExport = new CacheExportService($secretLoader, $filesystem); // Future enhancement
5854
}
5955

src/Commands/RunCommand.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
use Illuminate\Filesystem\Filesystem;
66
use STS\Keep\Commands\Concerns\GathersInput;
77
use STS\Keep\Commands\Concerns\ResolvesTemplates;
8-
use STS\Keep\Data\Collections\SecretsCollection;
8+
use STS\Keep\Data\Collections\SecretCollection;
99
use STS\Keep\Data\Template;
1010
use STS\Keep\Exceptions\ProcessExecutionException;
1111
use STS\Keep\Facades\Keep;
12-
use STS\Keep\Services\EnvironmentBuilder;
1312
use STS\Keep\Services\ProcessRunner;
1413

1514
use function Laravel\Prompts\error;
@@ -35,14 +34,12 @@ class RunCommand extends BaseCommand
3534

3635
protected $description = 'Execute a subprocess with secrets injected as environment variables';
3736

38-
protected EnvironmentBuilder $environmentBuilder;
3937
protected ProcessRunner $processRunner;
4038

4139
public function __construct(Filesystem $filesystem)
4240
{
4341
parent::__construct($filesystem);
4442

45-
$this->environmentBuilder = new EnvironmentBuilder();
4643
$this->processRunner = new ProcessRunner();
4744
}
4845

@@ -105,7 +102,9 @@ protected function process(): int
105102
);
106103

107104
// Clear sensitive data from memory
108-
$this->environmentBuilder->clearEnvironment($environment);
105+
foreach ($environment as $key => $value) {
106+
unset($environment[$key]);
107+
}
109108

110109
// Return the exit code from the subprocess
111110
if (!$result->successful) {
@@ -183,7 +182,7 @@ protected function buildFromTemplate(?string $templatePath, string $stage, strin
183182
}
184183

185184
// Build environment from template
186-
return $this->environmentBuilder->buildFromTemplate($template, $vaults, $inheritCurrent);
185+
return $template->toEnvironment($vaults, $inheritCurrent);
187186
}
188187

189188
/**
@@ -217,6 +216,6 @@ protected function buildFromVault(string $stage, string $vaultSlug, bool $inheri
217216
note("Injecting {$secrets->count()} secret(s) as environment variables");
218217

219218
// Build environment
220-
return $this->environmentBuilder->buildFromSecrets($secrets, $inheritCurrent);
219+
return $secrets->toEnvironment($inheritCurrent);
221220
}
222221
}

src/Commands/SetCommand.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace STS\Keep\Commands;
44

5-
use STS\Keep\Services\SecretKeyValidator;
6-
75
class SetCommand extends BaseCommand
86
{
97
public $signature = 'set
@@ -17,11 +15,7 @@ class SetCommand extends BaseCommand
1715

1816
public function process()
1917
{
20-
// Validate key using the shared validator service
2118
$key = $this->key();
22-
$validator = new SecretKeyValidator();
23-
$validator->validate($key);
24-
2519
$context = $this->vaultContext();
2620
$secret = $context->createVault()->set($key, $this->value(), $this->secure());
2721

src/Commands/StageAddCommand.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
namespace STS\Keep\Commands;
44

5+
use Exception;
56
use STS\Keep\Commands\Concerns\ValidatesStages;
7+
use STS\Keep\Data\Collections\PermissionsCollection;
68
use STS\Keep\Data\Settings;
7-
use STS\Keep\Services\VaultPermissionTester;
9+
use STS\Keep\Data\VaultStagePermissions;
10+
use STS\Keep\Facades\Keep;
11+
use STS\Keep\Services\LocalStorage;
812

913
use function Laravel\Prompts\confirm;
1014
use function Laravel\Prompts\error;
@@ -47,8 +51,7 @@ public function process()
4751
$this->line('You can now use this stage with any Keep command using --stage='.$stageName);
4852

4953
// Verify and cache permissions for all vaults with the new stage
50-
$tester = new VaultPermissionTester();
51-
$collection = $tester->testNewStageAcrossVaults($stageName);
54+
$collection = $this->testNewStageAcrossVaults($stageName);
5255

5356
if (!$collection->isEmpty()) {
5457
info('\nVerified vault permissions for the new stage:');
@@ -95,4 +98,30 @@ private function addStage(Settings $settings, string $stageName): void
9598
'created_at' => $settings->createdAt(),
9699
])->save();
97100
}
101+
102+
protected function testNewStageAcrossVaults(string $stageName): PermissionsCollection
103+
{
104+
$vaultNames = Keep::getConfiguredVaults()->keys()->toArray();
105+
$collection = new PermissionsCollection();
106+
$localStorage = new LocalStorage();
107+
108+
foreach ($vaultNames as $vaultName) {
109+
try {
110+
$vault = Keep::vault($vaultName, $stageName);
111+
$results = $vault->testPermissions();
112+
$permission = VaultStagePermissions::fromTestResults($vaultName, $stageName, $results);
113+
} catch (Exception $e) {
114+
$permission = VaultStagePermissions::fromError($vaultName, $stageName, $e->getMessage());
115+
}
116+
117+
$collection->addPermission($permission);
118+
119+
// Update stored permissions for this vault
120+
$existingPermissions = $localStorage->getVaultPermissions($vaultName) ?? [];
121+
$existingPermissions[$stageName] = $permission->permissions();
122+
$localStorage->saveVaultPermissions($vaultName, $existingPermissions);
123+
}
124+
125+
return $collection;
126+
}
98127
}

src/Commands/VaultEditCommand.php

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace STS\Keep\Commands;
44

5+
use Exception;
56
use STS\Keep\Commands\Concerns\ConfiguresVaults;
7+
use STS\Keep\Data\Collections\PermissionsCollection;
68
use STS\Keep\Data\VaultConfig;
9+
use STS\Keep\Data\VaultStagePermissions;
710
use STS\Keep\Facades\Keep;
8-
use STS\Keep\Services\VaultPermissionTester;
11+
use STS\Keep\Services\LocalStorage;
912

1013
use function Laravel\Prompts\error;
1114
use function Laravel\Prompts\info;
@@ -116,8 +119,7 @@ protected function process(): int
116119
info("✅ Vault configuration updated and renamed from '{$slug}' to '{$newSlug}'");
117120

118121
// Refresh permissions for new slug
119-
$tester = new VaultPermissionTester();
120-
$tester->testVaultAcrossStages($newSlug);
122+
$this->testVaultAcrossStages($newSlug);
121123
} else {
122124
// Save with same slug
123125
$updatedConfig['slug'] = $slug;
@@ -126,12 +128,35 @@ protected function process(): int
126128
info("✅ Vault '{$slug}' configuration updated successfully");
127129

128130
// Refresh permissions
129-
$tester = new VaultPermissionTester();
130-
$tester->testVaultAcrossStages($slug);
131+
$this->testVaultAcrossStages($slug);
131132
}
132133

133134
return self::SUCCESS;
134135
}
136+
137+
protected function testVaultAcrossStages(string $vaultName): void
138+
{
139+
$stages = Keep::getStages();
140+
$collection = new PermissionsCollection();
141+
$localStorage = new LocalStorage();
142+
$vaultPermissions = [];
143+
144+
foreach ($stages as $stage) {
145+
try {
146+
$vault = Keep::vault($vaultName, $stage);
147+
$results = $vault->testPermissions();
148+
$permission = VaultStagePermissions::fromTestResults($vaultName, $stage, $results);
149+
} catch (Exception $e) {
150+
$permission = VaultStagePermissions::fromError($vaultName, $stage, $e->getMessage());
151+
}
152+
153+
$collection->addPermission($permission);
154+
$vaultPermissions[$stage] = $permission->permissions();
155+
}
156+
157+
// Persist permissions for this vault
158+
$localStorage->saveVaultPermissions($vaultName, $vaultPermissions);
159+
}
135160

136161
private function findVaultClass(string $driver): ?string
137162
{

src/Commands/VerifyCommand.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
namespace STS\Keep\Commands;
44

5+
use Exception;
56
use Illuminate\Support\Str;
67
use STS\Keep\Data\Collections\PermissionsCollection;
78
use STS\Keep\Data\Context;
9+
use STS\Keep\Data\VaultStagePermissions;
810
use STS\Keep\Facades\Keep;
9-
use STS\Keep\Services\VaultPermissionTester;
1011

1112
use function Laravel\Prompts\spin;
1213
use function Laravel\Prompts\table;
@@ -22,17 +23,15 @@ class VerifyCommand extends BaseCommand
2223

2324
public function process()
2425
{
25-
$tester = new VaultPermissionTester();
26-
2726
/** @var PermissionsCollection $collection */
28-
$collection = spin(function () use ($tester) {
27+
$collection = spin(function () {
2928
// If --context is provided, use specific contexts
3029
if ($this->option('context')) {
3130
$contexts = $this->parseContexts();
3231
$vaults = array_unique(array_map(fn($c) => $c->vault, $contexts));
3332
$stages = array_unique(array_map(fn($c) => $c->stage, $contexts));
3433

35-
return $tester->testBulkPermissions($vaults, $stages);
34+
return $this->testBulkPermissions($vaults, $stages);
3635
}
3736

3837
// Otherwise use existing logic
@@ -45,12 +44,33 @@ public function process()
4544
? [$this->option('stage')]
4645
: Keep::getAllStages();
4746

48-
return $tester->testBulkPermissions($vaults, $stages);
47+
return $this->testBulkPermissions($vaults, $stages);
4948
}, 'Checking vault access permissions...');
5049

5150
$this->displayResults($collection->toDisplayArray());
5251
}
5352

53+
protected function testBulkPermissions(array $vaultNames, array $stages): PermissionsCollection
54+
{
55+
$collection = new PermissionsCollection();
56+
57+
foreach ($vaultNames as $vaultName) {
58+
foreach ($stages as $stage) {
59+
try {
60+
$vault = Keep::vault($vaultName, $stage);
61+
$results = $vault->testPermissions();
62+
$permission = VaultStagePermissions::fromTestResults($vaultName, $stage, $results);
63+
} catch (Exception $e) {
64+
$permission = VaultStagePermissions::fromError($vaultName, $stage, $e->getMessage());
65+
}
66+
67+
$collection->addPermission($permission);
68+
}
69+
}
70+
71+
return $collection;
72+
}
73+
5474

5575
protected function displayResults(array $results): void
5676
{

0 commit comments

Comments
 (0)