diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 297585562..8c1769181 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1363,7 +1363,7 @@ private function save_runtime_environment_variables() $envs_base64 = base64_encode($environment_variables->implode("\n")); // Write .env file to workdir (for container runtime) - $this->application_deployment_queue->addLogEntry('Creating .env file with runtime variables for build phase.', hidden: true); + $this->application_deployment_queue->addLogEntry('Creating .env file with runtime variables for container.', hidden: true); $this->execute_remote_command( [ executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/.env > /dev/null"), @@ -1402,7 +1402,7 @@ private function generate_buildtime_environment_variables() } $envs = collect([]); - $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); // Add COOLIFY variables $coolify_envs->each(function ($item, $key) use ($envs) { @@ -1979,7 +1979,6 @@ private function set_coolify_variables() $this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} "; } $this->coolify_variables .= "COOLIFY_RESOURCE_UUID={$this->application->uuid} "; - $this->coolify_variables .= "COOLIFY_CONTAINER_NAME={$this->container_name} "; } private function check_git_if_build_needed() @@ -2230,7 +2229,7 @@ private function generate_nixpacks_env_variables() } // Add COOLIFY_* environment variables to Nixpacks build context - $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); $coolify_envs->each(function ($value, $key) { $this->env_nixpacks_args->push("--env {$key}={$value}"); }); @@ -2238,7 +2237,7 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = $this->env_nixpacks_args->implode(' '); } - private function generate_coolify_env_variables(): Collection + private function generate_coolify_env_variables(bool $forBuildTime = false): Collection { $coolify_envs = collect([]); $local_branch = $this->branch; @@ -2273,8 +2272,11 @@ private function generate_coolify_env_variables(): Collection if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { $coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid); } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + // Only add COOLIFY_CONTAINER_NAME for runtime (not build-time) - it changes every deployment and breaks Docker cache + if (! $forBuildTime) { + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + } } } @@ -2311,8 +2313,11 @@ private function generate_coolify_env_variables(): Collection if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) { $coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid); } - if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + // Only add COOLIFY_CONTAINER_NAME for runtime (not build-time) - it changes every deployment and breaks Docker cache + if (! $forBuildTime) { + if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name); + } } } @@ -2328,7 +2333,7 @@ private function generate_env_variables() $this->env_args = collect([]); $this->env_args->put('SOURCE_COMMIT', $this->commit); - $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); $coolify_envs->each(function ($value, $key) { $this->env_args->put($key, $value); }); @@ -2748,7 +2753,7 @@ private function build_image() } else { // Traditional build args approach - generate COOLIFY_ variables locally // Generate COOLIFY_ variables locally for build args - $coolify_envs = $this->generate_coolify_env_variables(); + $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); $coolify_envs->each(function ($value, $key) { $this->build_args->push("--build-arg '{$key}'"); }); @@ -3294,7 +3299,9 @@ private function generate_build_secrets(Collection $variables) private function generate_secrets_hash($variables) { if (! $this->secrets_hash_key) { - $this->secrets_hash_key = bin2hex(random_bytes(32)); + // Use APP_KEY as deterministic hash key to preserve Docker build cache + // Random keys would change every deployment, breaking cache even when secrets haven't changed + $this->secrets_hash_key = config('app.key'); } if ($variables instanceof Collection) { diff --git a/app/Livewire/SharedVariables/Environment/Show.php b/app/Livewire/SharedVariables/Environment/Show.php index bee757a64..0bdc1503f 100644 --- a/app/Livewire/SharedVariables/Environment/Show.php +++ b/app/Livewire/SharedVariables/Environment/Show.php @@ -5,6 +5,7 @@ use App\Models\Application; use App\Models\Project; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Support\Facades\DB; use Livewire\Component; class Show extends Component @@ -19,7 +20,11 @@ class Show extends Component public array $parameters; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey', 'environmentVariableDeleted' => '$refresh']; + public string $view = 'normal'; + + public ?string $variables = null; + + protected $listeners = ['refreshEnvs' => 'refreshEnvs', 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs']; public function saveKey($data) { @@ -39,6 +44,7 @@ public function saveKey($data) 'team_id' => currentTeam()->id, ]); $this->environment->refresh(); + $this->getDevView(); } catch (\Throwable $e) { return handleError($e, $this); } @@ -49,6 +55,120 @@ public function mount() $this->parameters = get_route_parameters(); $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->firstOrFail(); $this->environment = $this->project->environments()->where('uuid', request()->route('environment_uuid'))->firstOrFail(); + $this->getDevView(); + } + + public function switch() + { + $this->authorize('view', $this->environment); + $this->view = $this->view === 'normal' ? 'dev' : 'normal'; + $this->getDevView(); + } + + public function getDevView() + { + $this->variables = $this->formatEnvironmentVariables($this->environment->environment_variables->sortBy('key')); + } + + private function formatEnvironmentVariables($variables) + { + return $variables->map(function ($item) { + if ($item->is_shown_once) { + return "$item->key=(Locked Secret, delete and add again to change)"; + } + if ($item->is_multiline) { + return "$item->key=(Multiline environment variable, edit in normal view)"; + } + + return "$item->key=$item->value"; + })->join("\n"); + } + + public function submit() + { + try { + $this->authorize('update', $this->environment); + $this->handleBulkSubmit(); + $this->getDevView(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->refreshEnvs(); + } + } + + private function handleBulkSubmit() + { + $variables = parseEnvFormatToArray($this->variables); + $changesMade = false; + + DB::transaction(function () use ($variables, &$changesMade) { + // Delete removed variables + $deletedCount = $this->deleteRemovedVariables($variables); + if ($deletedCount > 0) { + $changesMade = true; + } + + // Update or create variables + $updatedCount = $this->updateOrCreateVariables($variables); + if ($updatedCount > 0) { + $changesMade = true; + } + }); + + // Only dispatch success after transaction has committed + if ($changesMade) { + $this->dispatch('success', 'Environment variables updated.'); + } + } + + private function deleteRemovedVariables($variables) + { + $variablesToDelete = $this->environment->environment_variables()->whereNotIn('key', array_keys($variables))->get(); + + if ($variablesToDelete->isEmpty()) { + return 0; + } + + $this->environment->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + + return $variablesToDelete->count(); + } + + private function updateOrCreateVariables($variables) + { + $count = 0; + foreach ($variables as $key => $value) { + $found = $this->environment->environment_variables()->where('key', $key)->first(); + + if ($found) { + if (! $found->is_shown_once && ! $found->is_multiline) { + if ($found->value !== $value) { + $found->value = $value; + $found->save(); + $count++; + } + } + } else { + $this->environment->environment_variables()->create([ + 'key' => $key, + 'value' => $value, + 'is_multiline' => false, + 'is_literal' => false, + 'type' => 'environment', + 'team_id' => currentTeam()->id, + ]); + $count++; + } + } + + return $count; + } + + public function refreshEnvs() + { + $this->environment->refresh(); + $this->getDevView(); } public function render() diff --git a/app/Livewire/SharedVariables/Project/Show.php b/app/Livewire/SharedVariables/Project/Show.php index 712a9960b..b205ea1ec 100644 --- a/app/Livewire/SharedVariables/Project/Show.php +++ b/app/Livewire/SharedVariables/Project/Show.php @@ -4,6 +4,7 @@ use App\Models\Project; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Support\Facades\DB; use Livewire\Component; class Show extends Component @@ -12,7 +13,11 @@ class Show extends Component public Project $project; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => '$refresh']; + public string $view = 'normal'; + + public ?string $variables = null; + + protected $listeners = ['refreshEnvs' => 'refreshEnvs', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs']; public function saveKey($data) { @@ -32,6 +37,7 @@ public function saveKey($data) 'team_id' => currentTeam()->id, ]); $this->project->refresh(); + $this->getDevView(); } catch (\Throwable $e) { return handleError($e, $this); } @@ -46,6 +52,114 @@ public function mount() return redirect()->route('dashboard'); } $this->project = $project; + $this->getDevView(); + } + + public function switch() + { + $this->authorize('view', $this->project); + $this->view = $this->view === 'normal' ? 'dev' : 'normal'; + $this->getDevView(); + } + + public function getDevView() + { + $this->variables = $this->formatEnvironmentVariables($this->project->environment_variables->sortBy('key')); + } + + private function formatEnvironmentVariables($variables) + { + return $variables->map(function ($item) { + if ($item->is_shown_once) { + return "$item->key=(Locked Secret, delete and add again to change)"; + } + if ($item->is_multiline) { + return "$item->key=(Multiline environment variable, edit in normal view)"; + } + + return "$item->key=$item->value"; + })->join("\n"); + } + + public function submit() + { + try { + $this->authorize('update', $this->project); + $this->handleBulkSubmit(); + $this->getDevView(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->refreshEnvs(); + } + } + + private function handleBulkSubmit() + { + $variables = parseEnvFormatToArray($this->variables); + + $changesMade = DB::transaction(function () use ($variables) { + // Delete removed variables + $deletedCount = $this->deleteRemovedVariables($variables); + + // Update or create variables + $updatedCount = $this->updateOrCreateVariables($variables); + + return $deletedCount > 0 || $updatedCount > 0; + }); + + if ($changesMade) { + $this->dispatch('success', 'Environment variables updated.'); + } + } + + private function deleteRemovedVariables($variables) + { + $variablesToDelete = $this->project->environment_variables()->whereNotIn('key', array_keys($variables))->get(); + + if ($variablesToDelete->isEmpty()) { + return 0; + } + + $this->project->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + + return $variablesToDelete->count(); + } + + private function updateOrCreateVariables($variables) + { + $count = 0; + foreach ($variables as $key => $value) { + $found = $this->project->environment_variables()->where('key', $key)->first(); + + if ($found) { + if (! $found->is_shown_once && ! $found->is_multiline) { + if ($found->value !== $value) { + $found->value = $value; + $found->save(); + $count++; + } + } + } else { + $this->project->environment_variables()->create([ + 'key' => $key, + 'value' => $value, + 'is_multiline' => false, + 'is_literal' => false, + 'type' => 'project', + 'team_id' => currentTeam()->id, + ]); + $count++; + } + } + + return $count; + } + + public function refreshEnvs() + { + $this->project->refresh(); + $this->getDevView(); } public function render() diff --git a/app/Livewire/SharedVariables/Team/Index.php b/app/Livewire/SharedVariables/Team/Index.php index 82473528c..e420686f0 100644 --- a/app/Livewire/SharedVariables/Team/Index.php +++ b/app/Livewire/SharedVariables/Team/Index.php @@ -4,6 +4,7 @@ use App\Models\Team; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Support\Facades\DB; use Livewire\Component; class Index extends Component @@ -12,7 +13,11 @@ class Index extends Component public Team $team; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => '$refresh']; + public string $view = 'normal'; + + public ?string $variables = null; + + protected $listeners = ['refreshEnvs' => 'refreshEnvs', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs']; public function saveKey($data) { @@ -32,6 +37,7 @@ public function saveKey($data) 'team_id' => currentTeam()->id, ]); $this->team->refresh(); + $this->getDevView(); } catch (\Throwable $e) { return handleError($e, $this); } @@ -40,6 +46,119 @@ public function saveKey($data) public function mount() { $this->team = currentTeam(); + $this->getDevView(); + } + + public function switch() + { + $this->authorize('view', $this->team); + $this->view = $this->view === 'normal' ? 'dev' : 'normal'; + $this->getDevView(); + } + + public function getDevView() + { + $this->variables = $this->formatEnvironmentVariables($this->team->environment_variables->sortBy('key')); + } + + private function formatEnvironmentVariables($variables) + { + return $variables->map(function ($item) { + if ($item->is_shown_once) { + return "$item->key=(Locked Secret, delete and add again to change)"; + } + if ($item->is_multiline) { + return "$item->key=(Multiline environment variable, edit in normal view)"; + } + + return "$item->key=$item->value"; + })->join("\n"); + } + + public function submit() + { + try { + $this->authorize('update', $this->team); + $this->handleBulkSubmit(); + $this->getDevView(); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->refreshEnvs(); + } + } + + private function handleBulkSubmit() + { + $variables = parseEnvFormatToArray($this->variables); + $changesMade = false; + + DB::transaction(function () use ($variables, &$changesMade) { + // Delete removed variables + $deletedCount = $this->deleteRemovedVariables($variables); + if ($deletedCount > 0) { + $changesMade = true; + } + + // Update or create variables + $updatedCount = $this->updateOrCreateVariables($variables); + if ($updatedCount > 0) { + $changesMade = true; + } + }); + + if ($changesMade) { + $this->dispatch('success', 'Environment variables updated.'); + } + } + + private function deleteRemovedVariables($variables) + { + $variablesToDelete = $this->team->environment_variables()->whereNotIn('key', array_keys($variables))->get(); + + if ($variablesToDelete->isEmpty()) { + return 0; + } + + $this->team->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + + return $variablesToDelete->count(); + } + + private function updateOrCreateVariables($variables) + { + $count = 0; + foreach ($variables as $key => $value) { + $found = $this->team->environment_variables()->where('key', $key)->first(); + + if ($found) { + if (! $found->is_shown_once && ! $found->is_multiline) { + if ($found->value !== $value) { + $found->value = $value; + $found->save(); + $count++; + } + } + } else { + $this->team->environment_variables()->create([ + 'key' => $key, + 'value' => $value, + 'is_multiline' => false, + 'is_literal' => false, + 'type' => 'team', + 'team_id' => currentTeam()->id, + ]); + $count++; + } + } + + return $count; + } + + public function refreshEnvs() + { + $this->team->refresh(); + $this->getDevView(); } public function render() diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index c4d77979f..4a0faaec1 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -962,6 +962,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--shm-size' => 'shm_size', '--gpus' => 'gpus', '--hostname' => 'hostname', + '--entrypoint' => 'entrypoint', ]); foreach ($matches as $match) { $option = $match[1]; @@ -982,6 +983,38 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $options[$option] = array_unique($options[$option]); } } + if ($option === '--entrypoint') { + $value = null; + // Match --entrypoint=value or --entrypoint value + // Handle quoted strings with escaped quotes: --entrypoint "python -c \"print('hi')\"" + // Pattern matches: double-quoted (with escapes), single-quoted (with escapes), or unquoted values + if (preg_match( + '/--entrypoint(?:=|\s+)(?"(?:\\\\.|[^"])*"|\'(?:\\\\.|[^\'])*\'|[^\s]+)/', + $custom_docker_run_options, + $entrypoint_matches + )) { + $rawValue = $entrypoint_matches['raw']; + // Handle double-quoted strings: strip quotes and unescape special characters + if (str_starts_with($rawValue, '"') && str_ends_with($rawValue, '"')) { + $inner = substr($rawValue, 1, -1); + // Unescape backslash sequences: \" \$ \` \\ + $value = preg_replace('/\\\\(["$`\\\\])/', '$1', $inner); + } elseif (str_starts_with($rawValue, "'") && str_ends_with($rawValue, "'")) { + // Handle single-quoted strings: just strip quotes (no unescaping per shell rules) + $value = substr($rawValue, 1, -1); + } else { + // Handle unquoted values + $value = $rawValue; + } + } + + if ($value && trim($value) !== '') { + $options[$option][] = $value; + $options[$option] = array_values(array_unique($options[$option])); + } + + continue; + } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; $options[$option][] = $value; @@ -1022,6 +1055,12 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { $compose_options->put($mapping[$option], $value[0]); } + } elseif ($option === '--entrypoint') { + if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { + // Docker compose accepts entrypoint as either a string or an array + // Keep it as a string for simplicity - docker compose will handle it + $compose_options->put($mapping[$option], $value[0]); + } } elseif ($option === '--gpus') { $payload = [ 'driver' => 'nvidia', diff --git a/config/constants.php b/config/constants.php index 24aae9c81..5135b1fe0 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.448', + 'version' => '4.0.0-beta.449', 'helper_version' => '1.0.12', 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/other/nightly/versions.json b/other/nightly/versions.json index e946d3bb6..8911c7d7b 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.448" + "version": "4.0.0-beta.449" }, "nightly": { - "version": "4.0.0-beta.449" + "version": "4.0.0-beta.450" }, "helper": { "version": "1.0.12" diff --git a/resources/views/components/forms/env-var-input.blade.php b/resources/views/components/forms/env-var-input.blade.php index 833de7190..53a6b21ec 100644 --- a/resources/views/components/forms/env-var-input.blade.php +++ b/resources/views/components/forms/env-var-input.blade.php @@ -20,22 +20,12 @@ availableVars: @js($availableVars), scopeUrls: @js($scopeUrls), - isAutocompleteDisabled() { - const hasAnyVars = Object.values(this.availableVars).some(vars => vars.length > 0); - return !hasAnyVars; - }, - handleInput() { const input = this.$refs.input; if (!input) return; const value = input.value || ''; - if (this.isAutocompleteDisabled()) { - this.showDropdown = false; - return; - } - this.cursorPosition = input.selectionStart || 0; const textBeforeCursor = value.substring(0, this.cursorPosition); diff --git a/resources/views/livewire/shared-variables/environment/show.blade.php b/resources/views/livewire/shared-variables/environment/show.blade.php index 0799a7422..fde2d0ae8 100644 --- a/resources/views/livewire/shared-variables/environment/show.blade.php +++ b/resources/views/livewire/shared-variables/environment/show.blade.php @@ -9,17 +9,26 @@ @endcan + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }}
You can use these variables anywhere with @{{ environment.VARIABLENAME }}
-
- @forelse ($environment->environment_variables->sort()->sortBy('key') as $env) - - @empty -
No environment variables found.
- @endforelse -
+ @if ($view === 'normal') +
+ @forelse ($environment->environment_variables->sort()->sortBy('key') as $env) + + @empty +
No environment variables found.
+ @endforelse +
+ @else +
+ + Save All Environment Variables +
+ @endif diff --git a/resources/views/livewire/shared-variables/project/show.blade.php b/resources/views/livewire/shared-variables/project/show.blade.php index 7db3b61a2..f89ad9ce7 100644 --- a/resources/views/livewire/shared-variables/project/show.blade.php +++ b/resources/views/livewire/shared-variables/project/show.blade.php @@ -9,6 +9,7 @@ @endcan + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }}
You can use these variables anywhere with
@@ -16,12 +17,20 @@
-
- @forelse ($project->environment_variables->sort()->sortBy('key') as $env) - - @empty -
No environment variables found.
- @endforelse -
+ @if ($view === 'normal') +
+ @forelse ($project->environment_variables->sort()->sortBy('key') as $env) + + @empty +
No environment variables found.
+ @endforelse +
+ @else +
+ + Save All Environment Variables +
+ @endif diff --git a/resources/views/livewire/shared-variables/team/index.blade.php b/resources/views/livewire/shared-variables/team/index.blade.php index 1fbdfc2c5..fcfca35fb 100644 --- a/resources/views/livewire/shared-variables/team/index.blade.php +++ b/resources/views/livewire/shared-variables/team/index.blade.php @@ -9,18 +9,27 @@ @endcan + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }}
You can use these variables anywhere with @{{ team.VARIABLENAME }}
-
- @forelse ($team->environment_variables->sort()->sortBy('key') as $env) - - @empty -
No environment variables found.
- @endforelse -
+ @if ($view === 'normal') +
+ @forelse ($team->environment_variables->sort()->sortBy('key') as $env) + + @empty +
No environment variables found.
+ @endforelse +
+ @else +
+ + Save All Environment Variables +
+ @endif diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index a7829a534..5d9dcd174 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -125,3 +125,76 @@ ], ]); }); + +test('ConvertEntrypointSimple', function () { + $input = '--entrypoint /bin/sh'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => '/bin/sh', + ]); +}); + +test('ConvertEntrypointWithEquals', function () { + $input = '--entrypoint=/bin/bash'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => '/bin/bash', + ]); +}); + +test('ConvertEntrypointWithArguments', function () { + $input = '--entrypoint "sh -c npm install"'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'sh -c npm install', + ]); +}); + +test('ConvertEntrypointWithSingleQuotes', function () { + $input = "--entrypoint 'memcached -m 256'"; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'memcached -m 256', + ]); +}); + +test('ConvertEntrypointWithOtherOptions', function () { + $input = '--entrypoint /bin/bash --cap-add SYS_ADMIN --privileged'; + $output = convertDockerRunToCompose($input); + expect($output)->toHaveKeys(['entrypoint', 'cap_add', 'privileged']) + ->and($output['entrypoint'])->toBe('/bin/bash') + ->and($output['cap_add'])->toBe(['SYS_ADMIN']) + ->and($output['privileged'])->toBe(true); +}); + +test('ConvertEntrypointComplex', function () { + $input = '--entrypoint "sh -c \'npm install && npm start\'"'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "sh -c 'npm install && npm start'", + ]); +}); + +test('ConvertEntrypointWithEscapedDoubleQuotes', function () { + $input = '--entrypoint "python -c \"print(\'hi\')\""'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "python -c \"print('hi')\"", + ]); +}); + +test('ConvertEntrypointWithEscapedSingleQuotesInDoubleQuotes', function () { + $input = '--entrypoint "sh -c \"echo \'hello\'\""'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "sh -c \"echo 'hello'\"", + ]); +}); + +test('ConvertEntrypointSingleQuotedWithDoubleQuotesInside', function () { + $input = '--entrypoint \'python -c "print(\"hi\")"\''; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'python -c "print(\"hi\")"', + ]); +}); diff --git a/versions.json b/versions.json index e946d3bb6..8911c7d7b 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.448" + "version": "4.0.0-beta.449" }, "nightly": { - "version": "4.0.0-beta.449" + "version": "4.0.0-beta.450" }, "helper": { "version": "1.0.12"