From 6f75c601c378601f5a69f3e46c0d79b191b0af1b Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Sat, 3 Oct 2026 19:22:36 +0200
Subject: [PATCH] feat(application): show the full file path inline and warn on
a repeated base directory
---
.../forms/repository-paths.blade.php | 47 +++++++++++++
.../project/application/general.blade.php | 70 ++-----------------
...ub-private-repository-deploy-key.blade.php | 25 +------
.../new/github-private-repository.blade.php | 25 +------
.../new/gitlab-private-repository.blade.php | 25 +------
.../new/public-git-repository.blade.php | 25 +------
.../Browser/ApplicationConfigurationTest.php | 32 +++++++++
.../NewApplicationRepositoryPathsTest.php | 31 ++++++++
8 files changed, 129 insertions(+), 151 deletions(-)
create mode 100644 resources/views/components/forms/repository-paths.blade.php
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 e0ec7b5add..650a348d8a 100644
--- a/resources/views/livewire/project/application/general.blade.php
+++ b/resources/views/livewire/project/application/general.blade.php
@@ -143,36 +143,9 @@
@if ($buildPack === 'dockercompose')
-
-
-
-
+
@else
-
-
- @if ($buildPack === 'dockerfile' && !$application->dockerfile)
-
- @endif
-
+
@if ($buildPack === 'dockerfile')
@endif
@endif
-
-
+
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
@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 22be530c95..80aeb0ee33 100644
--- a/resources/views/livewire/project/new/github-private-repository.blade.php
+++ b/resources/views/livewire/project/new/github-private-repository.blade.php
@@ -135,28 +135,9 @@
@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 4060fff309..608b543d9c 100644
--- a/resources/views/livewire/project/new/gitlab-private-repository.blade.php
+++ b/resources/views/livewire/project/new/gitlab-private-repository.blade.php
@@ -133,28 +133,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 7e120b8539..6e5995ba5d 100644
--- a/resources/views/livewire/project/new/public-git-repository.blade.php
+++ b/resources/views/livewire/project/new/public-git-repository.blade.php
@@ -84,28 +84,9 @@
@if ($build_pack === 'dockercompose')
-
-
-
-
- Resolved file:
-
-
-
+
@else
diff --git a/tests/v4/Browser/ApplicationConfigurationTest.php b/tests/v4/Browser/ApplicationConfigurationTest.php
index f7778b501b..7d65f22ae8 100644
--- a/tests/v4/Browser/ApplicationConfigurationTest.php
+++ b/tests/v4/Browser/ApplicationConfigurationTest.php
@@ -156,6 +156,38 @@ 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']);
diff --git a/tests/v4/Browser/NewApplicationRepositoryPathsTest.php b/tests/v4/Browser/NewApplicationRepositoryPathsTest.php
index f7970fd02a..7571eb06a6 100644
--- a/tests/v4/Browser/NewApplicationRepositoryPathsTest.php
+++ b/tests/v4/Browser/NewApplicationRepositoryPathsTest.php
@@ -1,5 +1,6 @@
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');
+});