From 6f8cf50402496f2a8798d680b43e4203375878ba Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:15:46 +0100 Subject: [PATCH] feat(env): show compose defaults for empty variables in developer view Co-Authored-By: Claude Opus 5.5 --- .../Shared/EnvironmentVariable/All.php | 19 ++++- ...nmentVariableDevViewComposeDefaultTest.php | 70 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/EnvironmentVariableDevViewComposeDefaultTest.php diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 8ffae393a7..7a6abd2b2b 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -991,10 +991,27 @@ class All extends Component return $variables ->reject(fn ($item): bool => $this->isProtectedEnvironmentVariable($item->key)) - ->map(fn ($item) => formatEnvVariableForDevView($item, $canViewValues)) + ->map(fn ($item) => $this->composeDefaultHint($item).formatEnvVariableForDevView($item, $canViewValues)) ->join("\n"); } + /** + * An empty value makes the deployment use the Compose default, so show that default above the line. + */ + private function composeDefaultHint(EnvironmentVariable $variable): string + { + if (filled($variable->value) || ! $this->showsHardcodedEnvironmentVariables()) { + return ''; + } + + $default = $this->composeInfoFor($variable->key)['default'] ?? null; + if (blank($default) || str_contains($default, "\n")) { + return ''; + } + + return "# Compose default: {$default}\n"; + } + public function switch() { $this->authorize('view', $this->resource); diff --git a/tests/Feature/EnvironmentVariableDevViewComposeDefaultTest.php b/tests/Feature/EnvironmentVariableDevViewComposeDefaultTest.php new file mode 100644 index 0000000000..5c258420e1 --- /dev/null +++ b/tests/Feature/EnvironmentVariableDevViewComposeDefaultTest.php @@ -0,0 +1,70 @@ + 0]); + + $this->team = Team::factory()->create(); + $this->owner = User::factory()->create(); + $this->member = User::factory()->create(); + $this->team->members()->attach($this->owner, ['role' => 'owner']); + $this->team->members()->attach($this->member, ['role' => 'member']); + + $server = Server::factory()->create(['team_id' => $this->team->id]); + $destination = $server->standaloneDockers()->firstOrFail(); + $project = Project::factory()->create(['team_id' => $this->team->id]); + + $this->application = Application::factory()->create([ + 'build_pack' => 'dockercompose', + 'docker_compose_raw' => "services:\n app:\n image: nginx:alpine\n environment:\n - 'BACKUP_KEEP=\${BACKUP_KEEP:-3}'\n - 'LOG_LEVEL=\${LOG_LEVEL:-info}'\n - 'APP_NAME=\${APP_NAME}'\n", + 'environment_id' => $project->environments()->firstOrFail()->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ])->fresh(); + EnvironmentVariable::query()->delete(); + + foreach (['BACKUP_KEEP' => '', 'LOG_LEVEL' => 'debug', 'APP_NAME' => ''] as $key => $value) { + EnvironmentVariable::create([ + 'key' => $key, + 'value' => $value, + 'resourceable_type' => Application::class, + 'resourceable_id' => $this->application->id, + ]); + } + + $this->devView = fn () => Livewire::test(All::class, ['resource' => $this->application]) + ->call('loadEnvironmentVariables') + ->call('switch'); +}); + +it('shows the compose default above an empty variable and keeps it empty when saved', function () { + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + $component = ($this->devView)(); + + expect($component->get('variables'))->toBe("# Compose default: 3\nBACKUP_KEEP=\nLOG_LEVEL=debug\nAPP_NAME="); + + $component->call('submit')->assertNotDispatched('error'); + + expect($this->application->environment_variables()->where('key', 'BACKUP_KEEP')->value('value'))->toBe(''); +}); + +it('does not show compose defaults to a member', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + expect(($this->devView)()->get('variables'))->not->toContain('Compose default'); +});