From f86ccfaa9af572a5487da8ea46b0a125a4854cf6 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Tue, 18 Nov 2025 13:07:12 +0100
Subject: [PATCH] fix: auto-inject -f and --env-file flags into custom Docker
Compose commands
---
app/Livewire/Project/Application/General.php | 30 ++
bootstrap/helpers/docker.php | 28 ++
.../project/application/general.blade.php | 22 +-
...cationDeploymentCustomBuildCommandTest.php | 368 +++++++++++++++++-
4 files changed, 441 insertions(+), 7 deletions(-)
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 7d3d64bee..5817d2883 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -1005,4 +1005,34 @@ public function getDetectedPortInfoProperty(): ?array
'isEmpty' => $isEmpty,
];
}
+
+ public function getDockerComposeBuildCommandPreviewProperty(): string
+ {
+ if (! $this->dockerComposeCustomBuildCommand) {
+ return '';
+ }
+
+ // Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml)
+ // Actual deployment uses absolute path: /artifacts/{deployment_uuid}{base_directory}{docker_compose_location}
+ return injectDockerComposeFlags(
+ $this->dockerComposeCustomBuildCommand,
+ ".{$this->baseDirectory}{$this->dockerComposeLocation}",
+ '/artifacts/build-time.env'
+ );
+ }
+
+ public function getDockerComposeStartCommandPreviewProperty(): string
+ {
+ if (! $this->dockerComposeCustomStartCommand) {
+ return '';
+ }
+
+ // Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml)
+ // Placeholder {workdir}/.env shows it's the workdir .env file (runtime env, not build-time)
+ return injectDockerComposeFlags(
+ $this->dockerComposeCustomStartCommand,
+ ".{$this->baseDirectory}{$this->dockerComposeLocation}",
+ '{workdir}/.env'
+ );
+ }
}
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index c62c2ad8e..37e705518 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -1272,3 +1272,31 @@ function generateDockerEnvFlags($variables): string
})
->implode(' ');
}
+
+/**
+ * Auto-inject -f and --env-file flags into a docker compose command if not already present
+ *
+ * @param string $command The docker compose command to modify
+ * @param string $composeFilePath The path to the compose file
+ * @param string $envFilePath The path to the .env file
+ * @return string The modified command with injected flags
+ */
+function injectDockerComposeFlags(string $command, string $composeFilePath, string $envFilePath): string
+{
+ $dockerComposeReplacement = 'docker compose';
+
+ // Add -f flag if not present (checks for both -f and --file with various formats)
+ // Detects: -f path, -f=path, -fpath (concatenated), --file path, --file=path with any whitespace (space, tab, newline)
+ if (! preg_match('/(?:^|\s)(?:-f(?:[=\s]|\S)|--file(?:=|\s))/', $command)) {
+ $dockerComposeReplacement .= " -f {$composeFilePath}";
+ }
+
+ // Add --env-file flag if not present (checks for --env-file with various formats)
+ // Detects: --env-file path, --env-file=path with any whitespace
+ if (! preg_match('/(?:^|\s)--env-file(?:=|\s)/', $command)) {
+ $dockerComposeReplacement .= " --env-file {$envFilePath}";
+ }
+
+ // Replace only first occurrence to avoid modifying comments/strings/chained commands
+ return preg_replace('/docker\s+compose/', $dockerComposeReplacement, $command, 1);
+}
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php
index 415a1d378..ad18aa77a 100644
--- a/resources/views/livewire/project/application/general.blade.php
+++ b/resources/views/livewire/project/application/general.blade.php
@@ -259,13 +259,31 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
+ @if ($this->dockerComposeCustomBuildCommand)
+
+
+
+ @endif
+ @if ($this->dockerComposeCustomStartCommand)
+
+
+
+ @endif
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
toStartWith('DOCKER_BUILDKIT=1');
expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
});
+
+// Tests for -f flag injection
+
+it('injects -f flag with compose file path into custom build command', function () {
+ $customCommand = 'docker compose build';
+ $composeFilePath = '/artifacts/deployment-uuid/backend/docker-compose.yaml';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, $composeFilePath, '/artifacts/build-time.env');
+
+ expect($customCommand)->toBe('docker compose -f /artifacts/deployment-uuid/backend/docker-compose.yaml --env-file /artifacts/build-time.env build');
+ expect($customCommand)->toContain('-f /artifacts/deployment-uuid/backend/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('does not duplicate -f flag when already present', function () {
+ $customCommand = 'docker compose -f ./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f ./custom/docker-compose.yaml build');
+ expect(substr_count($customCommand, ' -f '))->toBe(1);
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('does not duplicate --file flag when already present', function () {
+ $customCommand = 'docker compose --file ./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env --file ./custom/docker-compose.yaml build');
+ expect(substr_count($customCommand, '--file '))->toBe(1);
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('injects both -f and --env-file flags in single operation', function () {
+ $customCommand = 'docker compose build --no-cache';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/uuid/app/docker-compose.prod.yaml', '/artifacts/build-time.env');
+
+ expect($customCommand)->toBe('docker compose -f /artifacts/uuid/app/docker-compose.prod.yaml --env-file /artifacts/build-time.env build --no-cache');
+ expect($customCommand)->toContain('-f /artifacts/uuid/app/docker-compose.prod.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+ expect($customCommand)->toContain('build --no-cache');
+});
+
+it('respects user-provided -f and --env-file flags', function () {
+ $customCommand = 'docker compose -f ./my-compose.yaml --env-file .env build';
+
+ // Use the helper function - should not inject anything since both flags are already present
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ expect($customCommand)->toBe('docker compose -f ./my-compose.yaml --env-file .env build');
+ expect(substr_count($customCommand, ' -f '))->toBe(1);
+ expect(substr_count($customCommand, '--env-file'))->toBe(1);
+});
+
+// Tests for custom start command -f and --env-file injection
+
+it('injects -f and --env-file flags into custom start command', function () {
+ $customCommand = 'docker compose up -d';
+ $serverWorkdir = '/var/lib/docker/volumes/coolify-data/_data/applications/app-uuid';
+ $composeLocation = '/docker-compose.yaml';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, "{$serverWorkdir}{$composeLocation}", "{$serverWorkdir}/.env");
+
+ expect($customCommand)->toBe('docker compose -f /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/docker-compose.yaml --env-file /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/.env up -d');
+ expect($customCommand)->toContain('-f /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/.env');
+});
+
+it('does not duplicate -f flag in start command when already present', function () {
+ $customCommand = 'docker compose -f ./custom-compose.yaml up -d';
+ $serverWorkdir = '/var/lib/docker/volumes/coolify-data/_data/applications/app-uuid';
+ $composeLocation = '/docker-compose.yaml';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, "{$serverWorkdir}{$composeLocation}", "{$serverWorkdir}/.env");
+
+ expect($customCommand)->toBe('docker compose --env-file /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/.env -f ./custom-compose.yaml up -d');
+ expect(substr_count($customCommand, ' -f '))->toBe(1);
+ expect($customCommand)->toContain('--env-file');
+});
+
+it('does not duplicate --env-file flag in start command when already present', function () {
+ $customCommand = 'docker compose --env-file ./my.env up -d';
+ $serverWorkdir = '/var/lib/docker/volumes/coolify-data/_data/applications/app-uuid';
+ $composeLocation = '/docker-compose.yaml';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, "{$serverWorkdir}{$composeLocation}", "{$serverWorkdir}/.env");
+
+ expect($customCommand)->toBe('docker compose -f /var/lib/docker/volumes/coolify-data/_data/applications/app-uuid/docker-compose.yaml --env-file ./my.env up -d');
+ expect(substr_count($customCommand, '--env-file'))->toBe(1);
+ expect($customCommand)->toContain('-f');
+});
+
+it('respects both user-provided flags in start command', function () {
+ $customCommand = 'docker compose -f ./my-compose.yaml --env-file ./.env up -d';
+ $serverWorkdir = '/var/lib/docker/volumes/coolify-data/_data/applications/app-uuid';
+ $composeLocation = '/docker-compose.yaml';
+
+ // Use the helper function - should not inject anything since both flags are already present
+ $customCommand = injectDockerComposeFlags($customCommand, "{$serverWorkdir}{$composeLocation}", "{$serverWorkdir}/.env");
+
+ expect($customCommand)->toBe('docker compose -f ./my-compose.yaml --env-file ./.env up -d');
+ expect(substr_count($customCommand, ' -f '))->toBe(1);
+ expect(substr_count($customCommand, '--env-file'))->toBe(1);
+});
+
+it('injects both flags in start command with additional parameters', function () {
+ $customCommand = 'docker compose up -d --remove-orphans';
+ $serverWorkdir = '/workdir/app';
+ $composeLocation = '/backend/docker-compose.prod.yaml';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, "{$serverWorkdir}{$composeLocation}", "{$serverWorkdir}/.env");
+
+ expect($customCommand)->toBe('docker compose -f /workdir/app/backend/docker-compose.prod.yaml --env-file /workdir/app/.env up -d --remove-orphans');
+ expect($customCommand)->toContain('-f /workdir/app/backend/docker-compose.prod.yaml');
+ expect($customCommand)->toContain('--env-file /workdir/app/.env');
+ expect($customCommand)->toContain('--remove-orphans');
+});
+
+// Security tests: Prevent bypass vectors for flag detection
+
+it('detects -f flag with equals sign format (bypass vector)', function () {
+ $customCommand = 'docker compose -f=./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f= is already present
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f=./custom/docker-compose.yaml build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects --file flag with equals sign format (bypass vector)', function () {
+ $customCommand = 'docker compose --file=./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since --file= is already present
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env --file=./custom/docker-compose.yaml build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects --env-file flag with equals sign format (bypass vector)', function () {
+ $customCommand = 'docker compose --env-file=./custom/.env build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject --env-file flag since --env-file= is already present
+ expect($customCommand)->toBe('docker compose -f /artifacts/deployment-uuid/docker-compose.yaml --env-file=./custom/.env build');
+ expect($customCommand)->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->not->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects -f flag with tab character whitespace (bypass vector)', function () {
+ $customCommand = "docker compose\t-f\t./custom/docker-compose.yaml build";
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f with tab is already present
+ expect($customCommand)->toBe("docker compose --env-file /artifacts/build-time.env\t-f\t./custom/docker-compose.yaml build");
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects --env-file flag with tab character whitespace (bypass vector)', function () {
+ $customCommand = "docker compose\t--env-file\t./custom/.env build";
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject --env-file flag since --env-file with tab is already present
+ expect($customCommand)->toBe("docker compose -f /artifacts/deployment-uuid/docker-compose.yaml\t--env-file\t./custom/.env build");
+ expect($customCommand)->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->not->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects -f flag with multiple spaces (bypass vector)', function () {
+ $customCommand = 'docker compose -f ./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f with multiple spaces is already present
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f ./custom/docker-compose.yaml build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects --file flag with multiple spaces (bypass vector)', function () {
+ $customCommand = 'docker compose --file ./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since --file with multiple spaces is already present
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env --file ./custom/docker-compose.yaml build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects -f flag at start of command (edge case)', function () {
+ $customCommand = '-f ./custom/docker-compose.yaml docker compose build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f is at start of command
+ expect($customCommand)->toBe('-f ./custom/docker-compose.yaml docker compose --env-file /artifacts/build-time.env build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects --env-file flag at start of command (edge case)', function () {
+ $customCommand = '--env-file=./custom/.env docker compose build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject --env-file flag since --env-file is at start of command
+ expect($customCommand)->toBe('--env-file=./custom/.env docker compose -f /artifacts/deployment-uuid/docker-compose.yaml build');
+ expect($customCommand)->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->not->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('handles mixed whitespace correctly (comprehensive test)', function () {
+ $customCommand = "docker compose\t-f=./custom/docker-compose.yaml --env-file\t./custom/.env build";
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject any flags since both are already present with various whitespace
+ expect($customCommand)->toBe("docker compose\t-f=./custom/docker-compose.yaml --env-file\t./custom/.env build");
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->not->toContain('--env-file /artifacts/build-time.env');
+});
+
+// Tests for concatenated -f flag format (no space, no equals)
+
+it('detects -f flag in concatenated format -fvalue (bypass vector)', function () {
+ $customCommand = 'docker compose -f./custom/docker-compose.yaml build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f is concatenated with value
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f./custom/docker-compose.yaml build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+it('detects -f flag concatenated with path containing slash', function () {
+ $customCommand = 'docker compose -f/path/to/compose.yml up -d';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f is concatenated
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f/path/to/compose.yml up -d');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('-f/path/to/compose.yml');
+});
+
+it('detects -f flag concatenated at start of command', function () {
+ $customCommand = '-f./compose.yaml docker compose build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag since -f is already present (even at start)
+ expect($customCommand)->toBe('-f./compose.yaml docker compose --env-file /artifacts/build-time.env build');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+});
+
+it('detects concatenated -f flag with relative path', function () {
+ $customCommand = 'docker compose -f../docker-compose.prod.yaml build --no-cache';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Should NOT inject -f flag
+ expect($customCommand)->toBe('docker compose --env-file /artifacts/build-time.env -f../docker-compose.prod.yaml build --no-cache');
+ expect($customCommand)->not->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('-f../docker-compose.prod.yaml');
+});
+
+it('correctly injects when no -f flag is present (sanity check after concatenated fix)', function () {
+ $customCommand = 'docker compose build --no-cache';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/deployment-uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // SHOULD inject both flags
+ expect($customCommand)->toBe('docker compose -f /artifacts/deployment-uuid/docker-compose.yaml --env-file /artifacts/build-time.env build --no-cache');
+ expect($customCommand)->toContain('-f /artifacts/deployment-uuid/docker-compose.yaml');
+ expect($customCommand)->toContain('--env-file /artifacts/build-time.env');
+});
+
+// Edge case tests: First occurrence only replacement
+
+it('only replaces first docker compose occurrence in chained commands', function () {
+ $customCommand = 'docker compose pull && docker compose build';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Only the FIRST 'docker compose' should get the flags
+ expect($customCommand)->toBe('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env pull && docker compose build');
+ expect($customCommand)->toContain('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env pull');
+ expect($customCommand)->toContain(' && docker compose build');
+ // Verify the second occurrence is NOT modified
+ expect(substr_count($customCommand, '-f /artifacts/uuid/docker-compose.yaml'))->toBe(1);
+ expect(substr_count($customCommand, '--env-file /artifacts/build-time.env'))->toBe(1);
+});
+
+it('does not modify docker compose string in echo statements', function () {
+ $customCommand = 'docker compose build && echo "docker compose finished successfully"';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Only the FIRST 'docker compose' (the command) should get flags, NOT the echo message
+ expect($customCommand)->toBe('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env build && echo "docker compose finished successfully"');
+ expect($customCommand)->toContain('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env build');
+ expect($customCommand)->toContain('echo "docker compose finished successfully"');
+ // Verify echo message is NOT modified
+ expect(substr_count($customCommand, 'docker compose', 0))->toBe(2); // Two total occurrences
+ expect(substr_count($customCommand, '-f /artifacts/uuid/docker-compose.yaml'))->toBe(1); // Only first has flags
+});
+
+it('does not modify docker compose string in bash comments', function () {
+ $customCommand = 'docker compose build # This runs docker compose to build the image';
+
+ // Use the helper function
+ $customCommand = injectDockerComposeFlags($customCommand, '/artifacts/uuid/docker-compose.yaml', '/artifacts/build-time.env');
+
+ // Only the FIRST 'docker compose' (the command) should get flags, NOT the comment
+ expect($customCommand)->toBe('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env build # This runs docker compose to build the image');
+ expect($customCommand)->toContain('docker compose -f /artifacts/uuid/docker-compose.yaml --env-file /artifacts/build-time.env build');
+ expect($customCommand)->toContain('# This runs docker compose to build the image');
+ // Verify comment is NOT modified
+ expect(substr_count($customCommand, 'docker compose', 0))->toBe(2); // Two total occurrences
+ expect(substr_count($customCommand, '-f /artifacts/uuid/docker-compose.yaml'))->toBe(1); // Only first has flags
+});