From 0d27d3defff30fa512062842a50aea98644a08d6 Mon Sep 17 00:00:00 2001 From: Hagen Pommer Date: Fri, 24 Jul 2026 16:31:24 +0200 Subject: [PATCH 1/3] feat(dev): add local dev environment via docker compose --- .env.example | 2 ++ compose.yaml | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 compose.yaml diff --git a/.env.example b/.env.example index ca9d384..fb3cc33 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ APP_KEY= +# DEV_PHP_VERSION=8.5-dev-macos +DEV_PHP_VERSION=8.5-dev diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..069ef1b --- /dev/null +++ b/compose.yaml @@ -0,0 +1,12 @@ +services: + php: + image: "wodby/php:8.5-dev-macos" + environment: + PHP_EXTENSIONS_DISABLE: 'xhprof,spx' + healthcheck: + test: ["CMD", "php", "-v"] + interval: 10s + timeout: 5s + retries: 3 + volumes: + - .:/var/www/html From 653e5540b0161d43d4c53452290b139aaf155a5f Mon Sep 17 00:00:00 2001 From: Hagen Pommer Date: Fri, 24 Jul 2026 16:31:41 +0200 Subject: [PATCH 2/3] feat(dev): add make targets for local development --- Makefile | 3 +++ Makefile.dev.mk | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 Makefile.dev.mk diff --git a/Makefile b/Makefile index 14f12ed..92c3523 100644 --- a/Makefile +++ b/Makefile @@ -122,3 +122,6 @@ docker-test: docker-build docker-shell: docker-build docker run --rm -it -v "$$(pwd)":/workspace --entrypoint sh $(DOCKER_IMAGE) + + +include Makefile.dev.mk diff --git a/Makefile.dev.mk b/Makefile.dev.mk new file mode 100644 index 0000000..92df658 --- /dev/null +++ b/Makefile.dev.mk @@ -0,0 +1,17 @@ +up: + docker compose up -d + +down: + docker compose down + +restart: down up + +bash: + docker compose exec php bash + +test: + docker compose exec php bash -c 'XDEBUG_MODE=coverage composer test' + +check: + docker compose exec php bash -c 'composer lint && composer test:types' + From 833b1e675b0605a9bc6926f6e2c858a9036af5dd Mon Sep 17 00:00:00 2001 From: Hagen Pommer Date: Fri, 7 Aug 2026 10:54:41 +0200 Subject: [PATCH 3/3] test(init): make IO-error tests user independent --- tests/Feature/Commands/InitCommandTest.php | 39 ++++++++++------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/Feature/Commands/InitCommandTest.php b/tests/Feature/Commands/InitCommandTest.php index 95447a3..729df04 100644 --- a/tests/Feature/Commands/InitCommandTest.php +++ b/tests/Feature/Commands/InitCommandTest.php @@ -232,40 +232,35 @@ it('returns an IO error when a new .env cannot be written', function (): void { // No APP_KEY anywhere -> the command tries to create a fresh .env. - // Making the disk root read-only forces Storage::put() to fail, which - // bubbles up as a RuntimeException and is reported as an IO error. + // Forcing Storage::put() to return false (as it would on a failed write) + // bubbles up as a RuntimeException and is reported as an IO error. We mock + // rather than chmod because chmod is a no-op under root (e.g. in Docker). putenv('APP_KEY'); unset($_ENV['APP_KEY'], $_SERVER['APP_KEY']); - $root = Storage::path(''); - chmod($root, 0500); + Storage::shouldReceive('exists')->with('.env')->andReturn(false); + Storage::shouldReceive('put')->with('.env', Mockery::type('string'))->andReturn(false); - try { - $this->artisan('init') - ->expectsOutputToContain('permission denied') - ->assertExitCode(ExitCode::IoError->value); - } finally { - chmod($root, 0755); - } + $this->artisan('init') + ->expectsOutputToContain('permission denied') + ->assertExitCode(ExitCode::IoError->value); }); it('returns an IO error when an existing .env cannot be overwritten', function (): void { // An existing .env without an APP_KEY would normally be appended to, but - // making the file itself read-only forces the write to fail. + // forcing Storage::put() to return false makes the rewrite fail. We mock + // rather than chmod because chmod is a no-op under root (e.g. in Docker). putenv('APP_KEY'); unset($_ENV['APP_KEY'], $_SERVER['APP_KEY']); - Storage::put('.env', "DB_HOST=localhost\n"); - $path = Storage::path('.env'); - chmod($path, 0400); + Storage::shouldReceive('exists')->with('.env')->andReturn(true); + Storage::shouldReceive('get')->with('.env')->andReturn("DB_HOST=localhost\n"); + Storage::shouldReceive('path')->with('.env')->andReturn('/tmp/clonio-readonly.env'); + Storage::shouldReceive('put')->with('.env', Mockery::type('string'))->andReturn(false); - try { - $this->artisan('init') - ->expectsOutputToContain('permission denied') - ->assertExitCode(ExitCode::IoError->value); - } finally { - chmod($path, 0644); - } + $this->artisan('init') + ->expectsOutputToContain('permission denied') + ->assertExitCode(ExitCode::IoError->value); }); it('returns an IO error when an existing .env cannot be read', function (): void {