From d67fcd1dff7fee7bee21bf54e49f7d03f574d431 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Tue, 25 Nov 2025 11:23:18 +0100
Subject: [PATCH] feat: add magic variable detection and update UI behavior
accordingly
---
.../Shared/EnvironmentVariable/Show.php | 6 +
.../environment-variable/show.blade.php | 130 +++++++++-------
.../EnvironmentVariableMagicVariableTest.php | 141 ++++++++++++++++++
3 files changed, 227 insertions(+), 50 deletions(-)
create mode 100644 tests/Unit/EnvironmentVariableMagicVariableTest.php
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
index 75149a0d4..2a18be13c 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
@@ -24,6 +24,8 @@ class Show extends Component
public bool $isLocked = false;
+ public bool $isMagicVariable = false;
+
public bool $isSharedVariable = false;
public string $type;
@@ -146,9 +148,13 @@ public function syncData(bool $toModel = false)
public function checkEnvs()
{
$this->isDisabled = false;
+ $this->isMagicVariable = false;
+
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL') || str($this->env->key)->startsWith('SERVICE_NAME')) {
$this->isDisabled = true;
+ $this->isMagicVariable = true;
}
+
if ($this->env->is_shown_once) {
$this->isLocked = true;
}
diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php
index cc95939de..86faeeeb4 100644
--- a/resources/views/livewire/project/shared/environment-variable/show.blade.php
+++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php
@@ -40,10 +40,12 @@
-
-
+ @if (!$isMagicVariable)
+
+
+ @endif
@else
@if ($is_shared)
@else
@if ($isSharedVariable)
-
+ @if (!$isMagicVariable)
+
+ @endif
@else
@if (!$env->is_nixpacks)
- @if (!$env->is_nixpacks)
-
- @if ($is_multiline === false)
-
+ @if (!$isMagicVariable)
+ @if (!$env->is_nixpacks)
+
+ @if ($is_multiline === false)
+
+ @endif
@endif
@endif
@endif
@@ -86,10 +92,12 @@
-
-
+ @if (!$isMagicVariable)
+
+
+ @endif
@else
@if ($is_shared)
@else
@if ($isSharedVariable)
-
+ @if (!$isMagicVariable)
+
+ @endif
@else
-
- @if ($is_multiline === false)
-
+ @if (!$isMagicVariable)
+
+ @if ($is_multiline === false)
+
+ @endif
@endif
@endif
@endif
@@ -133,8 +145,10 @@
@endif
-
+ @if (!$isMagicVariable)
+
+ @endif
@else
@@ -164,8 +178,10 @@
@endif
-
+ @if (!$isMagicVariable)
+
+ @endif
@endcan
@can('update', $this->env)
@@ -179,10 +195,12 @@
-
-
+ @if (!$isMagicVariable)
+
+
+ @endif
@else
@if ($is_shared)
@else
@if ($isSharedVariable)
-
+ @if (!$isMagicVariable)
+
+ @endif
@else
@if (!$env->is_nixpacks)
- @if (!$env->is_nixpacks)
-
- @if ($is_multiline === false)
-
+ @if (!$isMagicVariable)
+ @if (!$env->is_nixpacks)
+
+ @if ($is_multiline === false)
+
+ @endif
@endif
@endif
@endif
@@ -214,8 +236,9 @@
@endif
-
- @if ($isDisabled)
+ @if (!$isMagicVariable)
+
+ @if ($isDisabled)
Update
Lock
- @endif
-
+ @endif
+
+ @endif
@else
@@ -247,10 +271,12 @@
-
-
+ @if (!$isMagicVariable)
+
+
+ @endif
@else
@if ($is_shared)
@else
@if ($isSharedVariable)
-
+ @if (!$isMagicVariable)
+
+ @endif
@else
-
- @if ($is_multiline === false)
-
+ @if (!$isMagicVariable)
+
+ @if ($is_multiline === false)
+
+ @endif
@endif
@endif
@endif
diff --git a/tests/Unit/EnvironmentVariableMagicVariableTest.php b/tests/Unit/EnvironmentVariableMagicVariableTest.php
new file mode 100644
index 000000000..ae85ba45f
--- /dev/null
+++ b/tests/Unit/EnvironmentVariableMagicVariableTest.php
@@ -0,0 +1,141 @@
+shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SERVICE_FQDN_DB');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeTrue();
+ expect($component->isDisabled)->toBeTrue();
+});
+
+test('SERVICE_URL variables are identified as magic variables', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SERVICE_URL_API');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeTrue();
+ expect($component->isDisabled)->toBeTrue();
+});
+
+test('SERVICE_NAME variables are identified as magic variables', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SERVICE_NAME');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeTrue();
+ expect($component->isDisabled)->toBeTrue();
+});
+
+test('regular variables are not magic variables', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('DATABASE_URL');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeFalse();
+ expect($component->isDisabled)->toBeFalse();
+});
+
+test('locked variables are not magic variables unless they start with SERVICE_', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SECRET_KEY');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(true);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeFalse();
+ expect($component->isLocked)->toBeTrue();
+});
+
+test('SERVICE_FQDN with port suffix is identified as magic variable', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SERVICE_FQDN_DB_5432');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeTrue();
+ expect($component->isDisabled)->toBeTrue();
+});
+
+test('SERVICE_URL with port suffix is identified as magic variable', function () {
+ $mock = Mockery::mock(EnvironmentVariable::class);
+ $mock->shouldReceive('getAttribute')
+ ->with('key')
+ ->andReturn('SERVICE_URL_API_8080');
+ $mock->shouldReceive('getAttribute')
+ ->with('is_shown_once')
+ ->andReturn(false);
+ $mock->shouldReceive('getMorphClass')
+ ->andReturn(EnvironmentVariable::class);
+
+ $component = new Show;
+ $component->env = $mock;
+ $component->checkEnvs();
+
+ expect($component->isMagicVariable)->toBeTrue();
+ expect($component->isDisabled)->toBeTrue();
+});