diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index db00977b8d..0a1fcdfc3d 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -460,7 +460,7 @@ class General extends Component $this->portsMappings = $this->application->ports_mappings; $this->customNetworkAliases = $this->application->custom_network_aliases; $this->dockerfile = $this->application->dockerfile; - $this->dockerfileLocation = $this->application->dockerfile_location; + $this->dockerfileLocation = $this->application->dockerfile_location ?? ($this->buildPack === 'dockerfile' ? '/Dockerfile' : null); $this->dockerfileTargetBuild = $this->application->dockerfile_target_build; $this->dockerRegistryImageName = $this->application->docker_registry_image_name; $this->dockerRegistryImageTag = $this->application->docker_registry_image_tag; diff --git a/resources/views/components/forms/repository-paths.blade.php b/resources/views/components/forms/repository-paths.blade.php new file mode 100644 index 0000000000..1b5c6f94f9 --- /dev/null +++ b/resources/views/components/forms/repository-paths.blade.php @@ -0,0 +1,47 @@ +@props([ + 'base', + 'file' => null, + 'fileLabel' => null, + 'defaultFile' => null, + 'disabled' => 'false', +]) + +
class('grid gap-4') }} x-data="{ + base: $wire.entangle('{{ $base }}'), + file: {{ $file ? "\$wire.entangle('{$file}')" : 'null' }}, + normalize(path) { + path = (path ?? '').trim().replace(/\/+$/, ''); + return path === '' || path.startsWith('/') ? path : '/' + path; + }, + get directory() { + return this.normalize(this.base); + }, + get location() { + return this.normalize(this.file) || '{{ $defaultFile }}'; + }, + get repeatsBase() { + return this.directory !== '' && this.location.startsWith(this.directory + '/'); + }, + get suggestion() { + return this.location.slice(this.directory.length); + }, +}"> + + @if ($file) + +

+ Resolved file: + +

+ + {{ $fileLabel }} is relative to the base directory, so it should not start with + again. + + + @endif + {{ $slot }} +
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index fa986e8532..b4ccb4cff1 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -144,35 +144,9 @@
@if ($buildPack === 'dockercompose')
-
- - -
+
@else -
- - @if ($buildPack === 'dockerfile' && !$application->dockerfile) - - @endif - - @if ($buildPack === 'dockerfile') - - @endif + @if ($application->could_set_build_commands()) @if ($application->settings->is_static) @endif @endif - -
+ @if ($this->application->is_github_based() && !$this->application->is_public_repository())
+ @if ($buildPack === 'dockerfile') + + @endif @if ($build_pack === 'dockercompose') -
- - -

- Resolved file: - -

-
+ @else diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php index 38bd93ded9..638ea322cf 100644 --- a/resources/views/livewire/project/new/github-private-repository.blade.php +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -154,28 +154,9 @@ @endif @if ($build_pack === 'dockercompose') -
- - -

- Resolved file: - -

-
+ @else diff --git a/resources/views/livewire/project/new/gitlab-private-repository.blade.php b/resources/views/livewire/project/new/gitlab-private-repository.blade.php index b047669071..30e87ee35e 100644 --- a/resources/views/livewire/project/new/gitlab-private-repository.blade.php +++ b/resources/views/livewire/project/new/gitlab-private-repository.blade.php @@ -131,28 +131,9 @@
@if ($build_pack === 'dockercompose') -
- - -

- Resolved file: - -

-
+ @else diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index 2d3d3ba596..b8ce0747e3 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -103,28 +103,9 @@ @endif @if ($build_pack === 'dockercompose') -
- - -

- Resolved file: - -

-
+ @else diff --git a/tests/Feature/ApplicationGeneralBuildpackSelectorTest.php b/tests/Feature/ApplicationGeneralBuildpackSelectorTest.php index d842d7c546..3d163f1a81 100644 --- a/tests/Feature/ApplicationGeneralBuildpackSelectorTest.php +++ b/tests/Feature/ApplicationGeneralBuildpackSelectorTest.php @@ -122,6 +122,26 @@ test('switching from railpack to compose preserves the existing application doma ->toBe('https://example.com,https://www.example.com'); }); +test('Dockerfile location defaults to the Dockerfile only for the Dockerfile build pack', function (string $buildPack, ?string $expected) { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => StandaloneDocker::class, + 'build_pack' => $buildPack, + 'static_image' => 'nginx:alpine', + 'base_directory' => '/', + 'is_http_basic_auth_enabled' => false, + 'redirect' => 'no', + ]); + + Livewire::test(General::class, ['application' => $application]) + ->assertSuccessful() + ->assertSet('dockerfileLocation', $expected); +})->with([ + 'Dockerfile' => ['dockerfile', '/Dockerfile'], + 'Nixpacks' => ['nixpacks', null], +]); + test('networking section hints that internal ports can be set per domain', function () { $application = Application::factory()->create([ 'environment_id' => $this->environment->id, diff --git a/tests/v4/Browser/ApplicationConfigurationTest.php b/tests/v4/Browser/ApplicationConfigurationTest.php index cb60e12949..7d65f22ae8 100644 --- a/tests/v4/Browser/ApplicationConfigurationTest.php +++ b/tests/v4/Browser/ApplicationConfigurationTest.php @@ -156,6 +156,59 @@ it('saves custom docker run options from the UI', function () { ->screenshot(filename: 'application-docker-run-options'); }); +it('warns when the Dockerfile location repeats the base directory and applies the suggested fix', function () { + $this->application->update([ + 'build_pack' => 'dockerfile', + 'base_directory' => '/apps/api', + 'dockerfile_location' => '/apps/api/Dockerfile', + ]); + + loginAndSkipBoarding(); + + $page = visit(applicationConfigurationUrl( + $this->stack['project'], + $this->stack['environment'], + $this->application + )); + + $page->assertSee('Base directory is repeated') + ->assertSee('/apps/api/apps/api/Dockerfile') + ->screenshot(filename: 'application-dockerfile-location-repeats-base-directory') + ->click('Use /Dockerfile') + ->assertDontSee('Base directory is repeated') + ->assertSee('/apps/api/Dockerfile'); + + submitLivewireForm($page); + + $page->assertNoJavaScriptErrors() + ->screenshot(filename: 'application-dockerfile-location-fixed'); + + $this->application->refresh(); + expect($this->application->base_directory)->toBe('/apps/api') + ->and($this->application->dockerfile_location)->toBe('/Dockerfile'); +}); + +it('restores the default file location when the field is cleared', function (string $buildPack, string $column, string $default) { + $this->application->update(['build_pack' => $buildPack, $column => '/docker/custom']); + + loginAndSkipBoarding(); + + $page = visit(applicationConfigurationUrl( + $this->stack['project'], + $this->stack['environment'], + $this->application + )); + + $page->clear("input[placeholder='{$default}']") + ->keys("input[placeholder='{$default}']", 'Tab') + ->assertValue("input[placeholder='{$default}']", $default) + ->assertNoJavaScriptErrors() + ->screenshot(filename: "application-{$buildPack}-location-cleared"); +})->with([ + 'Dockerfile' => ['dockerfile', 'dockerfile_location', '/Dockerfile'], + 'Docker Compose' => ['dockercompose', 'docker_compose_location', '/docker-compose.yaml'], +]); + it('opens environment variables page for the application', function () { loginAndSkipBoarding(); diff --git a/tests/v4/Browser/NewApplicationRepositoryPathsTest.php b/tests/v4/Browser/NewApplicationRepositoryPathsTest.php new file mode 100644 index 0000000000..7571eb06a6 --- /dev/null +++ b/tests/v4/Browser/NewApplicationRepositoryPathsTest.php @@ -0,0 +1,55 @@ +uuid}/environment/{$stack['environment']->uuid}/new?type=public&destination={$stack['destination']->uuid}"); + + $page->fill('repository_url', 'https://gitlab.com/example/monorepo.git') + ->click('Check repository') + ->assertSee('Build configuration'); + + selectListboxOption($page, 'build_pack', 'Docker Compose'); + + $page->clear("input[placeholder='/docker-compose.yaml']") + ->keys("input[placeholder='/docker-compose.yaml']", 'Tab') + ->assertValue("input[placeholder='/docker-compose.yaml']", '/docker-compose.yaml') + ->assertNoJavaScriptErrors() + ->screenshot(filename: 'new-application-compose-file-cleared'); +}); + +it('warns when the compose file repeats the base directory and creates the application with the suggested fix', function () { + $stack = seedBrowserResourceStack(); + loginAndSkipBoarding(); + + $page = visit("/project/{$stack['project']->uuid}/environment/{$stack['environment']->uuid}/new?type=public&destination={$stack['destination']->uuid}"); + + $page->fill('repository_url', 'https://gitlab.com/example/monorepo.git') + ->click('Check repository') + ->assertSee('Build configuration'); + + selectListboxOption($page, 'build_pack', 'Docker Compose'); + + $page->fill("input[placeholder='/']", 'apps/api/') + ->fill("input[placeholder='/docker-compose.yaml']", 'apps/api/compose.yaml') + ->assertSee('Base directory is repeated') + ->assertSee('/apps/api/apps/api/compose.yaml') + ->screenshot(filename: 'new-application-compose-file-repeats-base-directory') + ->click('button:has-text("Use /compose.yaml")') + ->assertDontSee('Base directory is repeated') + ->assertSee('/apps/api/compose.yaml') + ->assertNoJavaScriptErrors() + ->screenshot(filename: 'new-application-compose-file-fixed'); + + submitLivewireForm($page); + + $application = Application::query()->where('git_repository', 'https://gitlab.com/example/monorepo.git')->sole(); + expect($application->base_directory)->toBe('/apps/api') + ->and($application->docker_compose_location)->toBe('/compose.yaml'); +});