Merge remote-tracking branch 'origin/next' into v5-parallel-inertia-react

This commit is contained in:
Andras Bacsai
2026-07-06 23:19:55 +02:00
36 changed files with 305 additions and 966 deletions
-6
View File
@@ -29,12 +29,6 @@ DB_PORT=5432
# DB_WRITE_PASSWORD=
# DB_STICKY=true
# Ray Configuration
# Set to true to enable Ray
RAY_ENABLED=false
# Set custom ray port
# RAY_PORT=
# Enable Laravel Telescope for debugging
TELESCOPE_ENABLED=false
@@ -143,8 +143,6 @@ class StartDatabaseProxy
)
);
ray("Database proxy for {$database->name} disabled due to non-transient error: {$e->getMessage()}");
return;
}
-21
View File
@@ -26,22 +26,14 @@ class DeleteServer
);
}
ray($server ? 'Deleting server from Coolify' : 'Server already deleted from Coolify, skipping Coolify deletion');
// If server is already deleted from Coolify, skip this part
if (! $server) {
return; // Server already force deleted from Coolify
}
ray('force deleting server from Coolify', ['server_id' => $server->id]);
try {
$server->forceDelete();
} catch (\Throwable $e) {
ray('Failed to force delete server from Coolify', [
'error' => $e->getMessage(),
'server_id' => $server->id,
]);
logger()->error('Failed to force delete server from Coolify', [
'error' => $e->getMessage(),
'server_id' => $server->id,
@@ -66,10 +58,6 @@ class DeleteServer
}
if (! $token) {
ray('No Hetzner token found for team, skipping Hetzner deletion', [
'team_id' => $teamId,
'hetzner_server_id' => $hetznerServerId,
]);
return;
}
@@ -77,16 +65,7 @@ class DeleteServer
$hetznerService = new HetznerService($token->token);
$hetznerService->deleteServer($hetznerServerId);
ray('Deleted server from Hetzner', [
'hetzner_server_id' => $hetznerServerId,
'team_id' => $teamId,
]);
} catch (\Throwable $e) {
ray('Failed to delete server from Hetzner', [
'error' => $e->getMessage(),
'hetzner_server_id' => $hetznerServerId,
'team_id' => $teamId,
]);
// Log the error but don't prevent the server from being deleted from Coolify
logger()->error('Failed to delete server from Hetzner', [
@@ -3609,7 +3609,7 @@ class ApplicationsController extends Controller
'team_id' => $teamId,
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
'force_rebuild' => $force,
'instant_deploy' => $instant_deploy,
]);
@@ -3617,7 +3617,7 @@ class ApplicationsController extends Controller
return response()->json(
[
'message' => 'Deployment request queued.',
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
],
200
);
@@ -3803,13 +3803,13 @@ class ApplicationsController extends Controller
'team_id' => $teamId,
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
]);
return response()->json(
[
'message' => 'Restart request queued.',
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
],
);
}
@@ -425,7 +425,7 @@ class DeployController extends Controller
}
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
}
@@ -471,7 +471,7 @@ class DeployController extends Controller
}
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
if ($deployment_uuid) {
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid]);
}
$message = $message->merge($return_message);
}
@@ -529,7 +529,7 @@ class DeployController extends Controller
'resource_type' => 'application',
'application_uuid' => $resource->uuid,
'application_name' => $resource->name,
'deployment_uuid' => $deployment_uuid?->toString(),
'deployment_uuid' => $deployment_uuid,
'force_rebuild' => $force,
'pull_request_id' => $pr,
]);
+1 -1
View File
@@ -162,7 +162,7 @@ class Bitbucket extends Controller
'mode' => 'manual',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
'commit' => $commit,
'repository' => $full_name ?? null,
]);
+1 -1
View File
@@ -148,7 +148,7 @@ class Gitea extends Controller
'mode' => 'manual',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
'commit' => data_get($payload, 'after'),
'repository' => $full_name ?? null,
]);
+1 -1
View File
@@ -190,7 +190,7 @@ class Gitlab extends Controller
'mode' => 'manual',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_uuid' => $deployment_uuid,
'commit' => data_get($payload, 'after'),
'repository' => $full_name ?? null,
]);
+1 -16
View File
@@ -57,13 +57,6 @@ class SendWebhookJob implements ShouldBeEncrypted, ShouldQueue
return;
}
if (isDev()) {
ray('Sending webhook notification', [
'url' => $this->webhookUrl,
'payload' => $this->payload,
]);
}
try {
$httpOptions = SafeWebhookUrl::httpClientOptions($this->webhookUrl);
} catch (\RuntimeException $e) {
@@ -75,14 +68,6 @@ class SendWebhookJob implements ShouldBeEncrypted, ShouldQueue
return;
}
$response = Http::withOptions($httpOptions)->post($this->webhookUrl, $this->payload);
if (isDev()) {
ray('Webhook response', [
'status' => $response->status(),
'body' => $response->body(),
'successful' => $response->successful(),
]);
}
Http::withOptions($httpOptions)->post($this->webhookUrl, $this->payload);
}
}
-1
View File
@@ -179,7 +179,6 @@ class ServerConnectionCheckJob implements ShouldBeEncrypted, ShouldQueue
$this->server->update(['hetzner_server_status' => $status]);
$this->server->hetzner_server_status = $status;
if ($status === 'off') {
ray('Server is powered off, marking as unreachable');
throw new \Exception('Server is powered off');
}
}
-2
View File
@@ -251,7 +251,6 @@ class GlobalSearch extends Component
$cacheKey = self::getCacheKey(auth()->user()->currentTeam()->id);
$this->allSearchableItems = Cache::remember($cacheKey, 300, function () {
ray()->showQueries();
$items = collect();
$team = auth()->user()->currentTeam();
@@ -530,7 +529,6 @@ class GlobalSearch extends Component
'search_text' => strtolower($server->name.' '.$server->ip.' '.$server->description.' server servers'),
];
});
ray($servers);
// Get all projects
$projects = Project::ownedByCurrentTeam()
->withCount(['environments', 'applications', 'services'])
-14
View File
@@ -168,13 +168,6 @@ class Webhook extends Component
$this->syncData(true);
refreshSession();
if (isDev()) {
ray('Webhook settings saved', [
'webhook_enabled' => $this->settings->webhook_enabled,
'webhook_url' => $this->settings->webhook_url,
]);
}
$this->dispatch('success', 'Settings saved.');
}
@@ -183,13 +176,6 @@ class Webhook extends Component
try {
$this->authorize('sendTest', $this->settings);
if (isDev()) {
ray('Sending test webhook notification', [
'team_id' => $this->team->id,
'webhook_url' => $this->settings->webhook_url,
]);
}
$this->team->notify(new Test(channel: 'webhook'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {
-1
View File
@@ -90,7 +90,6 @@ class Logs extends Component
}
} catch (\Exception $e) {
// Log error but don't fail the entire operation
ray("Error loading containers for server {$server->name}: ".$e->getMessage());
return [];
}
+1 -1
View File
@@ -356,7 +356,7 @@ class EnvironmentVariable extends BaseModel
private function set_environment_variables(?string $environment_variable = null): ?string
{
if (is_null($environment_variable) && $environment_variable === '') {
if (is_null($environment_variable)) {
return null;
}
$environment_variable = trim($environment_variable);
-1
View File
@@ -366,7 +366,6 @@ class LocalFileVolume extends BaseModel
return false;
} catch (\Throwable $e) {
ray($e->getMessage(), 'Error checking read-only volume');
return false;
}
-1
View File
@@ -187,7 +187,6 @@ class LocalPersistentVolume extends BaseModel
return false;
} catch (\Throwable $e) {
ray($e->getMessage(), 'Error checking read-only persistent volume');
return false;
}
-2
View File
@@ -1523,7 +1523,6 @@ $schema://$host {
public function generateCaCertificate()
{
try {
ray('Generating CA certificate for server', $this->id);
SslHelper::generateSslCertificate(
commonName: 'Coolify CA Certificate',
serverId: $this->id,
@@ -1531,7 +1530,6 @@ $schema://$host {
validityDays: 10 * 365
);
$caCertificate = $this->sslCertificates()->where('is_ca_certificate', true)->first();
ray('CA certificate generated', $caCertificate);
if ($caCertificate) {
$certificateContent = $caCertificate->ssl_certificate;
$caCertPath = config('constants.coolify.base_config_path').'/ssl/';
-1
View File
@@ -1583,7 +1583,6 @@ class Service extends BaseModel
$envs->push('SERVICE_NAME_'.str($serviceName)->replace('-', '_')->replace('.', '_')->upper().'='.$serviceName);
}
} catch (\Exception $e) {
ray($e->getMessage());
}
}
@@ -15,23 +15,11 @@ class WebhookChannel
$webhookSettings = $notifiable->webhookNotificationSettings;
if (! $webhookSettings || ! $webhookSettings->isEnabled() || ! $webhookSettings->webhook_url) {
if (isDev()) {
ray('Webhook notification skipped - not enabled or no URL configured');
}
return;
}
$payload = $notification->toWebhook();
if (isDev()) {
ray('Dispatching webhook notification', [
'notification' => get_class($notification),
'url' => $webhookSettings->webhook_url,
'payload' => $payload,
]);
}
SendWebhookJob::dispatch($payload, $webhookSettings->webhook_url);
}
}
@@ -17,8 +17,6 @@ class HetznerDeletionFailed extends CustomEmailNotification
public function via(object $notifiable): array
{
ray('hello');
ray($notifiable);
return $notifiable->getEnabledChannels('hetzner_deletion_failed');
}
+2 -9
View File
@@ -3,6 +3,7 @@
namespace App\Services;
use App\Exceptions\RateLimitException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
class HetznerService
@@ -24,7 +25,7 @@ class HetznerService
->timeout(30)
->retry(3, function (int $attempt, \Exception $exception) {
// Handle rate limiting (429 Too Many Requests)
if ($exception instanceof \Illuminate\Http\Client\RequestException) {
if ($exception instanceof RequestException) {
$response = $exception->response;
if ($response && $response->status() === 429) {
@@ -129,17 +130,9 @@ class HetznerService
public function createServer(array $params): array
{
ray('Hetzner createServer request', [
'endpoint' => '/servers',
'params' => $params,
]);
$response = $this->request('post', '/servers', $params);
ray('Hetzner createServer response', [
'response' => $response,
]);
return $response['server'] ?? [];
}
+12 -12
View File
@@ -3,6 +3,11 @@
namespace App\Traits;
use App\Livewire\GlobalSearch;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Database\Eloquent\Model;
trait ClearsGlobalSearchCache
@@ -20,7 +25,6 @@ trait ClearsGlobalSearchCache
}
} catch (\Throwable $e) {
// Silently fail cache clearing - don't break the save operation
ray('Failed to clear global search cache on saving: '.$e->getMessage());
}
});
@@ -33,7 +37,6 @@ trait ClearsGlobalSearchCache
}
} catch (\Throwable $e) {
// Silently fail cache clearing - don't break the create operation
ray('Failed to clear global search cache on creation: '.$e->getMessage());
}
});
@@ -46,7 +49,6 @@ trait ClearsGlobalSearchCache
}
} catch (\Throwable $e) {
// Silently fail cache clearing - don't break the delete operation
ray('Failed to clear global search cache on deletion: '.$e->getMessage());
}
});
}
@@ -58,14 +60,14 @@ trait ClearsGlobalSearchCache
$searchableFields = ['name', 'description'];
// Add model-specific searchable fields
if ($this instanceof \App\Models\Application) {
if ($this instanceof Application) {
$searchableFields[] = 'fqdn';
$searchableFields[] = 'docker_compose_domains';
} elseif ($this instanceof \App\Models\Server) {
} elseif ($this instanceof Server) {
$searchableFields[] = 'ip';
} elseif ($this instanceof \App\Models\Service) {
} elseif ($this instanceof Service) {
// Services don't have direct fqdn, but name and description are covered
} elseif ($this instanceof \App\Models\Project || $this instanceof \App\Models\Environment) {
} elseif ($this instanceof Project || $this instanceof Environment) {
// Projects and environments only have name and description as searchable
}
// Database models only have name and description as searchable
@@ -81,7 +83,6 @@ trait ClearsGlobalSearchCache
return false;
} catch (\Throwable $e) {
// If checking changes fails, assume changes exist to be safe
ray('Failed to check searchable changes: '.$e->getMessage());
return true;
}
@@ -91,18 +92,18 @@ trait ClearsGlobalSearchCache
{
try {
// For Project models (has direct team_id)
if ($this instanceof \App\Models\Project) {
if ($this instanceof Project) {
return $this->team_id ?? null;
}
// For Environment models (get team_id through project)
if ($this instanceof \App\Models\Environment) {
if ($this instanceof Environment) {
return $this->project?->team_id;
}
// For database models, team is accessed through environment.project.team
if (method_exists($this, 'team')) {
if ($this instanceof \App\Models\Server) {
if ($this instanceof Server) {
$team = $this->team;
} else {
$team = $this->team();
@@ -120,7 +121,6 @@ trait ClearsGlobalSearchCache
return null;
} catch (\Throwable $e) {
// If we can't determine team ID, return null
ray('Failed to get team ID for cache: '.$e->getMessage());
return null;
}
-1
View File
@@ -82,7 +82,6 @@ trait SshRetryable
$lastErrorMessage = '';
// Randomly fail the command with a key exchange error for testing
// if (random_int(1, 10) === 1) { // 10% chance to fail
// ray('SSH key exchange failed: kex_exchange_identification: read: Connection reset by peer');
// throw new \RuntimeException('SSH key exchange failed: kex_exchange_identification: read: Connection reset by peer');
// }
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
-2
View File
@@ -276,7 +276,6 @@ function getGithubCommitRangeFiles(?GithubApp $source, string $owner, string $re
return $files->pluck('filename')->filter()->values()->toArray();
} catch (Exception $e) {
ray('Error fetching GitHub commit range files: '.$e->getMessage());
return [];
}
@@ -302,7 +301,6 @@ function getGithubPullRequestFiles(?GithubApp $source, string $owner, string $re
return $files->pluck('filename')->filter()->values()->toArray();
} catch (Exception $e) {
ray('Error fetching GitHub PR files: '.$e->getMessage());
return [];
}
+1 -2
View File
@@ -18,8 +18,7 @@ function send_internal_notification(string $message): void
try {
$team = Team::find(0);
$team?->notify(new GeneralNotification($message));
} catch (\Throwable $e) {
ray($e->getMessage());
} catch (Throwable) {
}
}
+1 -3
View File
@@ -1480,9 +1480,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
}
$resource->docker_compose_raw = Yaml::dump($originalYaml, 10, 2);
} catch (Exception $e) {
} catch (Exception) {
// If parsing fails, keep the original docker_compose_raw unchanged
ray('Failed to update docker_compose_raw in applicationParser: '.$e->getMessage());
}
data_forget($resource, 'environment_variables');
@@ -2732,7 +2731,6 @@ function serviceParser(Service $resource): Collection
$resource->docker_compose_raw = Yaml::dump($originalYaml, 10, 2);
} catch (Exception $e) {
// If parsing fails, keep the original docker_compose_raw unchanged
ray('Failed to update docker_compose_raw in serviceParser: '.$e->getMessage());
}
data_forget($resource, 'environment_variables');
-4
View File
@@ -1764,7 +1764,6 @@ function validateDNSEntry(string $fqdn, Server $server)
$query = new DNSQuery($dns_server);
$results = $query->query($host, $type);
if ($results === false || $query->hasError()) {
ray('Error: '.$query->getLasterror());
} else {
foreach ($results as $result) {
if ($result->getType() == $type) {
@@ -3787,9 +3786,6 @@ function loggy($message = null, array $context = [])
if (! isDev()) {
return;
}
if (function_exists('ray') && config('app.debug')) {
ray($message, $context);
}
if (is_null($message)) {
return app('log');
}
-1
View File
@@ -51,7 +51,6 @@
"spatie/laravel-activitylog": "^4.11.0",
"spatie/laravel-data": "^4.19.1",
"spatie/laravel-markdown": "^2.7.1",
"spatie/laravel-ray": "^1.43.5",
"spatie/laravel-schemaless-attributes": "^2.5.1",
"spatie/url": "^2.4",
"stevebauman/purify": "^6.3.1",
Generated
+66 -726
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d7e1f3eef5ae01d358f9482007814245",
"content-hash": "3bc1d41688bd98785b556172ae2410ed",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -4826,134 +4826,6 @@
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "php-di/invoker",
"version": "2.3.7",
"source": {
"type": "git",
"url": "https://github.com/PHP-DI/Invoker.git",
"reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/3c1ddfdef181431fbc4be83378f6d036d59e81e1",
"reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1",
"shasum": ""
},
"require": {
"php": ">=7.3",
"psr/container": "^1.0|^2.0"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"mnapoli/hard-mode": "~0.3.0",
"phpunit/phpunit": "^9.0 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"Invoker\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Generic and extensible callable invoker",
"homepage": "https://github.com/PHP-DI/Invoker",
"keywords": [
"callable",
"dependency",
"dependency-injection",
"injection",
"invoke",
"invoker"
],
"support": {
"issues": "https://github.com/PHP-DI/Invoker/issues",
"source": "https://github.com/PHP-DI/Invoker/tree/2.3.7"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
}
],
"time": "2025-08-30T10:22:22+00:00"
},
{
"name": "php-di/php-di",
"version": "7.1.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-DI/PHP-DI.git",
"reference": "f88054cc052e40dbe7b383c8817c19442d480352"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f88054cc052e40dbe7b383c8817c19442d480352",
"reference": "f88054cc052e40dbe7b383c8817c19442d480352",
"shasum": ""
},
"require": {
"laravel/serializable-closure": "^1.0 || ^2.0",
"php": ">=8.0",
"php-di/invoker": "^2.0",
"psr/container": "^1.1 || ^2.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3",
"friendsofphp/proxy-manager-lts": "^1",
"mnapoli/phpunit-easymock": "^1.3",
"phpunit/phpunit": "^9.6 || ^10 || ^11",
"vimeo/psalm": "^5|^6"
},
"suggest": {
"friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"DI\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "The dependency injection container for humans",
"homepage": "https://php-di.org/",
"keywords": [
"PSR-11",
"container",
"container-interop",
"dependency injection",
"di",
"ioc",
"psr11"
],
"support": {
"issues": "https://github.com/PHP-DI/PHP-DI/issues",
"source": "https://github.com/PHP-DI/PHP-DI/tree/7.1.1"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/php-di/php-di",
"type": "tidelift"
}
],
"time": "2025-08-16T11:10:48+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -7099,70 +6971,6 @@
},
"time": "2024-11-07T21:57:40+00:00"
},
{
"name": "spatie/backtrace",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc",
"reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-json": "*",
"laravel/serializable-closure": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.3 || ^11.4.3",
"spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
"symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\Backtrace\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van de Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "A better backtrace",
"homepage": "https://github.com/spatie/backtrace",
"keywords": [
"Backtrace",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/backtrace/issues",
"source": "https://github.com/spatie/backtrace/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2026-03-11T13:48:28+00:00"
},
{
"name": "spatie/commonmark-shiki-highlighter",
"version": "2.5.2",
@@ -7535,95 +7343,6 @@
],
"time": "2026-05-19T14:06:37+00:00"
},
{
"name": "spatie/laravel-ray",
"version": "1.43.9",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ray.git",
"reference": "85137a6ea1d3ecd5ad3adcb43512fff9a5529e72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-ray/zipball/85137a6ea1d3ecd5ad3adcb43512fff9a5529e72",
"reference": "85137a6ea1d3ecd5ad3adcb43512fff9a5529e72",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.2",
"ext-json": "*",
"illuminate/contracts": "^7.20|^8.19|^9.0|^10.0|^11.0|^12.0|^13.0",
"illuminate/database": "^7.20|^8.19|^9.0|^10.0|^11.0|^12.0|^13.0",
"illuminate/queue": "^7.20|^8.19|^9.0|^10.0|^11.0|^12.0|^13.0",
"illuminate/support": "^7.20|^8.19|^9.0|^10.0|^11.0|^12.0|^13.0",
"php": "^7.4|^8.0",
"spatie/backtrace": "^1.7.1",
"spatie/ray": "^1.45.0",
"symfony/stopwatch": "4.2|^5.1|^6.0|^7.0|^8.0",
"zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0|^4.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.3",
"laravel/framework": "^7.20|^8.19|^9.0|^10.0|^11.0|^12.0|^13.0",
"laravel/pint": "^1.29",
"orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"pestphp/pest": "^1.22|^2.0|^3.0|^4.0",
"phpstan/phpstan": "^1.10.57|^2.0.2",
"phpunit/phpunit": "^9.3|^10.1|^11.0.10|^12.4",
"rector/rector": "^0.19.2|^1.0.1|^2.0.0",
"spatie/pest-plugin-snapshots": "^1.1|^2.0",
"symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3|^8.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\LaravelRay\\RayServiceProvider"
]
},
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Spatie\\LaravelRay\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Easily debug Laravel apps",
"homepage": "https://github.com/spatie/laravel-ray",
"keywords": [
"laravel-ray",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-ray/issues",
"source": "https://github.com/spatie/laravel-ray/tree/1.43.9"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2026-04-28T06:07:04+00:00"
},
{
"name": "spatie/laravel-schemaless-attributes",
"version": "2.6.0",
@@ -7830,91 +7549,6 @@
],
"time": "2026-04-28T06:26:02+00:00"
},
{
"name": "spatie/ray",
"version": "1.48.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/ray.git",
"reference": "974ac9c6e315033ab8ace883d60e094522f88ede"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/ray/zipball/974ac9c6e315033ab8ace883d60e094522f88ede",
"reference": "974ac9c6e315033ab8ace883d60e094522f88ede",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": "^7.4|^8.0",
"ramsey/uuid": "^3.0|^4.1",
"spatie/backtrace": "^1.7.1",
"spatie/macroable": "^1.0|^2.0",
"symfony/stopwatch": "^4.2|^5.1|^6.0|^7.0|^8.0",
"symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3|^8.0"
},
"require-dev": {
"illuminate/support": "^7.20|^8.18|^9.0|^10.0|^11.0|^12.0|^13.0",
"nesbot/carbon": "^2.63|^3.8.4",
"pestphp/pest": "^1.22",
"phpstan/phpstan": "^1.10.57|^2.0.3",
"phpunit/phpunit": "^9.5",
"rector/rector": "^0.19.2|^1.0.1|^2.0.0",
"spatie/phpunit-snapshot-assertions": "^4.2",
"spatie/test-time": "^1.2"
},
"bin": [
"bin/remove-ray.sh"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Spatie\\Ray\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Debug with Ray to fix problems faster",
"homepage": "https://github.com/spatie/ray",
"keywords": [
"ray",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/ray/issues",
"source": "https://github.com/spatie/ray/tree/1.48.0"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2026-03-31T12:44:31+00:00"
},
{
"name": "spatie/shiki-php",
"version": "2.4.0",
@@ -9576,90 +9210,6 @@
],
"time": "2026-04-10T16:19:22+00:00"
},
{
"name": "symfony/polyfill-iconv",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/2c5729fd241b4b22f6e4b436bc3354a4f262df57",
"reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-iconv": "*"
},
"suggest": {
"ext-iconv": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Iconv\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Iconv extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"iconv",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.37.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.38.1",
@@ -10995,72 +10545,6 @@
],
"time": "2026-03-28T09:44:51+00:00"
},
{
"name": "symfony/stopwatch",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/service-contracts": "^2.5|^3"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Stopwatch\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v8.0.8"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/string",
"version": "v8.0.13",
@@ -12265,214 +11749,6 @@
},
"time": "2018-08-08T15:08:14+00:00"
},
{
"name": "zbateson/mail-mime-parser",
"version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/zbateson/mail-mime-parser.git",
"reference": "3db681988a48fdffdba551dcc6b2f4c2da574540"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/3db681988a48fdffdba551dcc6b2f4c2da574540",
"reference": "3db681988a48fdffdba551dcc6b2f4c2da574540",
"shasum": ""
},
"require": {
"guzzlehttp/psr7": "^2.5",
"php": ">=8.1",
"php-di/php-di": "^6.0|^7.0",
"psr/log": "^1|^2|^3",
"zbateson/mb-wrapper": "^2.0 || ^3.0",
"zbateson/stream-decorators": "^2.1 || ^3.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"monolog/monolog": "^2|^3",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^10.5"
},
"suggest": {
"ext-iconv": "For best support/performance",
"ext-mbstring": "For best support/performance"
},
"type": "library",
"autoload": {
"psr-4": {
"ZBateson\\MailMimeParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Zaahid Bateson"
},
{
"name": "Contributors",
"homepage": "https://github.com/zbateson/mail-mime-parser/graphs/contributors"
}
],
"description": "MIME email message parser",
"homepage": "https://mail-mime-parser.org",
"keywords": [
"MimeMailParser",
"email",
"mail",
"mailparse",
"mime",
"mimeparse",
"parser",
"php-imap"
],
"support": {
"docs": "https://mail-mime-parser.org/#usage-guide",
"issues": "https://github.com/zbateson/mail-mime-parser/issues",
"source": "https://github.com/zbateson/mail-mime-parser"
},
"funding": [
{
"url": "https://github.com/zbateson",
"type": "github"
}
],
"time": "2026-03-11T18:03:41+00:00"
},
{
"name": "zbateson/mb-wrapper",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/zbateson/mb-wrapper.git",
"reference": "f0ee6af2712e92e52ee2552588cd69d21ab3363f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/f0ee6af2712e92e52ee2552588cd69d21ab3363f",
"reference": "f0ee6af2712e92e52ee2552588cd69d21ab3363f",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-mbstring": "^1.9"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*",
"phpunit/phpunit": "^10.0|^11.0"
},
"suggest": {
"ext-iconv": "For best support/performance",
"ext-mbstring": "For best support/performance"
},
"type": "library",
"autoload": {
"psr-4": {
"ZBateson\\MbWrapper\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Zaahid Bateson"
}
],
"description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation",
"keywords": [
"charset",
"encoding",
"http",
"iconv",
"mail",
"mb",
"mb_convert_encoding",
"mbstring",
"mime",
"multibyte",
"string"
],
"support": {
"issues": "https://github.com/zbateson/mb-wrapper/issues",
"source": "https://github.com/zbateson/mb-wrapper/tree/3.0.0"
},
"funding": [
{
"url": "https://github.com/zbateson",
"type": "github"
}
],
"time": "2026-02-13T19:33:26+00:00"
},
{
"name": "zbateson/stream-decorators",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/zbateson/stream-decorators.git",
"reference": "0c0e79a8c960055c0e2710357098eedc07e6697a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/0c0e79a8c960055c0e2710357098eedc07e6697a",
"reference": "0c0e79a8c960055c0e2710357098eedc07e6697a",
"shasum": ""
},
"require": {
"guzzlehttp/psr7": "^2.5",
"php": ">=8.1",
"zbateson/mb-wrapper": "^2.0 || ^3.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*",
"phpunit/phpunit": "^10.0 || ^11.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZBateson\\StreamDecorators\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Zaahid Bateson"
}
],
"description": "PHP psr7 stream decorators for mime message part streams",
"keywords": [
"base64",
"charset",
"decorators",
"mail",
"mime",
"psr7",
"quoted-printable",
"stream",
"uuencode"
],
"support": {
"issues": "https://github.com/zbateson/stream-decorators/issues",
"source": "https://github.com/zbateson/stream-decorators/tree/3.0.0"
},
"funding": [
{
"url": "https://github.com/zbateson",
"type": "github"
}
],
"time": "2026-02-13T19:45:34+00:00"
},
{
"name": "zircote/swagger-php",
"version": "5.8.3",
@@ -17474,6 +16750,70 @@
],
"time": "2026-04-16T21:33:58+00:00"
},
{
"name": "spatie/backtrace",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc",
"reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-json": "*",
"laravel/serializable-closure": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.3 || ^11.4.3",
"spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
"symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\Backtrace\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van de Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "A better backtrace",
"homepage": "https://github.com/spatie/backtrace",
"keywords": [
"Backtrace",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/backtrace/issues",
"source": "https://github.com/spatie/backtrace/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2026-03-11T13:48:28+00:00"
},
{
"name": "spatie/error-solutions",
"version": "1.1.3",
@@ -18149,5 +17489,5 @@
"php": "^8.4"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}
-108
View File
@@ -1,108 +0,0 @@
<?php
return [
/*
* This setting controls whether data should be sent to Ray.
*
* By default, `ray()` will only transmit data in non-production environments.
*/
'enable' => env('RAY_ENABLED', true),
/*
* When enabled, all cache events will automatically be sent to Ray.
*/
'send_cache_to_ray' => env('SEND_CACHE_TO_RAY', false),
/*
* When enabled, all things passed to `dump` or `dd`
* will be sent to Ray as well.
*/
'send_dumps_to_ray' => env('SEND_DUMPS_TO_RAY', true),
/*
* When enabled all job events will automatically be sent to Ray.
*/
'send_jobs_to_ray' => env('SEND_JOBS_TO_RAY', false),
/*
* When enabled, all things logged to the application log
* will be sent to Ray as well.
*/
'send_log_calls_to_ray' => env('SEND_LOG_CALLS_TO_RAY', true),
/*
* When enabled, all queries will automatically be sent to Ray.
*/
'send_queries_to_ray' => env('SEND_QUERIES_TO_RAY', false),
/**
* When enabled, all duplicate queries will automatically be sent to Ray.
*/
'send_duplicate_queries_to_ray' => env('SEND_DUPLICATE_QUERIES_TO_RAY', false),
/*
* When enabled, slow queries will automatically be sent to Ray.
*/
'send_slow_queries_to_ray' => env('SEND_SLOW_QUERIES_TO_RAY', false),
/**
* Queries that are longer than this number of milliseconds will be regarded as slow.
*/
'slow_query_threshold_in_ms' => env('RAY_SLOW_QUERY_THRESHOLD_IN_MS', 500),
/*
* When enabled, all requests made to this app will automatically be sent to Ray.
*/
'send_requests_to_ray' => env('SEND_REQUESTS_TO_RAY', false),
/**
* When enabled, all Http Client requests made by this app will be automatically sent to Ray.
*/
'send_http_client_requests_to_ray' => env('SEND_HTTP_CLIENT_REQUESTS_TO_RAY', false),
/*
* When enabled, all views that are rendered automatically be sent to Ray.
*/
'send_views_to_ray' => env('SEND_VIEWS_TO_RAY', false),
/*
* When enabled, all exceptions will be automatically sent to Ray.
*/
'send_exceptions_to_ray' => env('SEND_EXCEPTIONS_TO_RAY', true),
/*
* When enabled, all deprecation notices will be automatically sent to Ray.
*/
'send_deprecated_notices_to_ray' => env('SEND_DEPRECATED_NOTICES_TO_RAY', false),
/*
* The host used to communicate with the Ray app.
* When using Docker on Mac or Windows, you can replace localhost with 'host.docker.internal'
* When using Docker on Linux, you can replace localhost with '172.17.0.1'
* When using Homestead with the VirtualBox provider, you can replace localhost with '10.0.2.2'
* When using Homestead with the Parallels provider, you can replace localhost with '10.211.55.2'
*/
'host' => env('RAY_HOST', 'host.docker.internal'),
/*
* The port number used to communicate with the Ray app.
*/
'port' => env('RAY_PORT', 23517),
/*
* Absolute base path for your sites or projects in Homestead,
* Vagrant, Docker, or another remote development server.
*/
'remote_path' => env('RAY_REMOTE_PATH', null),
/*
* Absolute base path for your sites or projects on your local
* computer where your IDE or code editor is running on.
*/
'local_path' => env('RAY_LOCAL_PATH', null),
/*
* When this setting is enabled, the package will not try to format values sent to Ray.
*/
'always_send_raw_values' => false,
];
@@ -0,0 +1,108 @@
<?php
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Illuminate\Testing\Fluent\AssertableJson;
uses(RefreshDatabase::class);
beforeEach(function () {
Bus::fake([ApplicationDeploymentJob::class]);
InstanceSettings::unguarded(fn () => InstanceSettings::query()->updateOrCreate(
['id' => 0],
['is_api_enabled' => true],
));
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
session(['currentTeam' => $this->team]);
$this->token = $this->user->createToken('deployment-actions-test', ['deploy'])->plainTextToken;
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
});
function deploymentActionHeaders(string $token): array
{
return [
'Authorization' => 'Bearer '.$token,
'Content-Type' => 'application/json',
];
}
function makeDeploymentActionApplication(Environment $environment, StandaloneDocker $destination): Application
{
return Application::factory()->create([
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
'git_repository' => 'https://github.com/coollabsio/coolify',
'git_branch' => 'main',
'git_commit_sha' => 'HEAD',
]);
}
test('application start API returns queued deployment uuid as a string', function () {
$application = makeDeploymentActionApplication($this->environment, $this->destination);
$response = $this->withHeaders(deploymentActionHeaders($this->token))
->postJson("/api/v1/applications/{$application->uuid}/start");
$response->assertSuccessful()
->assertJson(fn (AssertableJson $json) => $json
->where('message', 'Deployment request queued.')
->whereType('deployment_uuid', 'string')
);
expect(ApplicationDeploymentQueue::query()->where('deployment_uuid', $response->json('deployment_uuid'))->exists())->toBeTrue();
});
test('application restart API returns queued deployment uuid as a string', function () {
$application = makeDeploymentActionApplication($this->environment, $this->destination);
$response = $this->withHeaders(deploymentActionHeaders($this->token))
->postJson("/api/v1/applications/{$application->uuid}/restart");
$response->assertSuccessful()
->assertJson(fn (AssertableJson $json) => $json
->where('message', 'Restart request queued.')
->whereType('deployment_uuid', 'string')
);
$deployment = ApplicationDeploymentQueue::query()
->where('deployment_uuid', $response->json('deployment_uuid'))
->first();
expect($deployment)->not->toBeNull()
->and($deployment->restart_only)->toBeTruthy();
});
test('deployment uuid strings are not converted as objects in API and webhook controllers', function () {
$files = [
app_path('Http/Controllers/Api/DeployController.php'),
app_path('Http/Controllers/Webhook/Bitbucket.php'),
app_path('Http/Controllers/Webhook/Gitea.php'),
app_path('Http/Controllers/Webhook/Gitlab.php'),
];
foreach ($files as $file) {
expect(file_get_contents($file))
->not->toContain('$deployment_uuid->toString()')
->not->toContain('$deployment_uuid?->toString()');
}
});
@@ -180,7 +180,7 @@ test('convertContainerEnvsToArray', function () {
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"RAY_ENABLED=true=123",
"FEATURE_FLAG_WITH_EQUALS=true=123",
"REGISTRY_URL=docker.io",
"SUBSCRIPTION_PROVIDER=stripe",
"TELESCOPE_ENABLED=false",
@@ -295,5 +295,5 @@ test('convertContainerEnvsToArray', function () {
}
]';
$envs = format_docker_envs_to_json($data);
$this->assertEquals('true=123', $envs->get('RAY_ENABLED'));
$this->assertEquals('true=123', $envs->get('FEATURE_FLAG_WITH_EQUALS'));
});
@@ -0,0 +1,45 @@
<?php
use App\Models\Application;
use App\Models\EnvironmentVariable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
uses(RefreshDatabase::class);
it('stores null environment variable values as database null', function () {
$env = EnvironmentVariable::create([
'key' => 'NULL_VALUE',
'value' => null,
'resourceable_type' => Application::class,
'resourceable_id' => 1,
]);
$rawValue = DB::table('environment_variables')
->where('id', $env->id)
->value('value');
expect($rawValue)->toBeNull();
$env->refresh();
expect($env->value)->toBeNull();
});
it('preserves intentional empty string environment variable values', function () {
$env = EnvironmentVariable::create([
'key' => 'EMPTY_STRING_VALUE',
'value' => '',
'resourceable_type' => Application::class,
'resourceable_id' => 1,
]);
$rawValue = DB::table('environment_variables')
->where('id', $env->id)
->value('value');
expect($rawValue)->not->toBeNull()
->and(decrypt($rawValue))->toBe('');
$env->refresh();
expect($env->value)->toBe('');
});
+1 -1
View File
@@ -218,7 +218,7 @@ describe('security UI audit logging', function () {
test('cloud provider token form does not contain debug ray calls', function () {
expect(file_get_contents(app_path('Livewire/Security/CloudProviderTokenForm.php')))
->not->toContain('ray(');
->not->toContain('ray'.'(');
});
});
@@ -95,6 +95,6 @@ it('ensures docker_compose_raw update is wrapped in try-catch for error handling
->toContain('// This keeps the original user input clean while preventing content reapplication')
->toContain('try {')
->toContain('$originalYaml = Yaml::parse($originalCompose);')
->toContain('} catch (\Exception $e) {')
->toContain("ray('Failed to update docker_compose_raw");
->toContain('} catch (Exception) {')
->toContain('// If parsing fails, keep the original docker_compose_raw unchanged');
});
+53
View File
@@ -0,0 +1,53 @@
<?php
function rayRemovalFiles(string $directory): array
{
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
$files = [];
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$files[] = $file->getPathname();
}
}
sort($files);
return $files;
}
function rayRemovalBasePath(string $path = ''): string
{
return dirname(__DIR__, 2).($path === '' ? '' : DIRECTORY_SEPARATOR.$path);
}
it('does not include Ray as a dependency', function () {
$composer = json_decode(file_get_contents(rayRemovalBasePath('composer.json')), true, flags: JSON_THROW_ON_ERROR);
$lock = json_decode(file_get_contents(rayRemovalBasePath('composer.lock')), true, flags: JSON_THROW_ON_ERROR);
$lockedPackageNames = collect([
...($lock['packages'] ?? []),
...($lock['packages-dev'] ?? []),
])->pluck('name');
expect($composer['require'] ?? [])->not->toHaveKey('spatie/laravel-ray')
->and($composer['require-dev'] ?? [])->not->toHaveKey('spatie/laravel-ray')
->and($lockedPackageNames)->not->toContain('spatie/laravel-ray', 'spatie/ray');
});
it('does not ship Ray configuration', function () {
expect(file_exists(rayRemovalBasePath('config/ray.php')))->toBeFalse();
});
it('does not call ray from application code', function () {
$files = [
...rayRemovalFiles(rayRemovalBasePath('app')),
...rayRemovalFiles(rayRemovalBasePath('bootstrap/helpers')),
];
$rayCalls = collect($files)
->filter(fn (string $file): bool => preg_match('/\bray\s*\(/', file_get_contents($file)) === 1)
->map(fn (string $file): string => str_replace(rayRemovalBasePath().'/', '', $file))
->values();
expect($rayCalls)->toBeEmpty();
});