From 5c4a41ef05cbb2f49fefadf67a07d456296535da Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 25 Sep 2026 19:40:34 -0500 Subject: [PATCH 1/2] correcting config::infer message Assisted-by: Claude Code --- resources/infection.json5 | 5 ----- src/Project/Config.php | 19 ++++++++++++++++--- tests/Unit/Project/ConfigTest.php | 30 +++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/resources/infection.json5 b/resources/infection.json5 index 24fe7a2..41f5e21 100644 --- a/resources/infection.json5 +++ b/resources/infection.json5 @@ -172,11 +172,6 @@ // array_values() changes nothing at runtime. It stays because // table() declares list> and PHPStan holds it to that. "ignore": ["Crest\\Command\\Route\\ListCommand::handle"] - }, - "UnwrapTrim": { - // is_dir() accepts a doubled separator, so trimming the psr-4 - // target changes nothing about which directory is found. - "ignore": ["Crest\\Project\\Config::infer"] } } } diff --git a/src/Project/Config.php b/src/Project/Config.php index 92a50d8..9065bce 100644 --- a/src/Project/Config.php +++ b/src/Project/Config.php @@ -193,10 +193,14 @@ private static function infer(string $directory): self throw new Exception('no crest.php and no composer.json found'); } - $psr4 = self::psr4Map($directory); + $psr4 = self::psr4Map($directory); + $missing = []; foreach ($psr4 as $prefix => $target) { - if (false === is_dir($directory . '/' . trim($target, '/'))) { + $target = trim($target, '/'); + + if (false === is_dir($directory . '/' . $target)) { + $missing[] = "'" . $target . "'"; continue; } @@ -210,7 +214,16 @@ private static function infer(string $directory): self ); } - throw new Exception('no crest.php and no usable psr-4 autoload entry found'); + if ([] === $missing) { + throw new Exception('no crest.php and no usable psr-4 autoload entry found'); + } + + throw new Exception( + sprintf( + 'no crest.php and no usable psr-4 autoload entry found; these psr-4 directories do not exist: %s', + implode(', ', $missing) + ) + ); } /** diff --git a/tests/Unit/Project/ConfigTest.php b/tests/Unit/Project/ConfigTest.php index 1ca8805..1d05436 100644 --- a/tests/Unit/Project/ConfigTest.php +++ b/tests/Unit/Project/ConfigTest.php @@ -52,6 +52,18 @@ public function testAdrGetsADefaultPathForEveryGeneratedArtifact(): void ); } + public function testComposerJsonWithoutPsr4EntriesThrows(): void + { + // No psr-4 entry at all, so there is no missing directory to name. + // Anchored: the message with a directory list starts the same way. + $this->writeComposerJson([]); + + $this->expectException(Exception::class); + $this->expectExceptionMessageMatches('/^no crest\.php and no usable psr-4 autoload entry found$/'); + + Config::discover($this->root); + } + public function testCrestPhpMayDeclareTheNamespaceExplicitly(): void { $this->writeComposerJson(['App\\' => 'src/']); @@ -290,6 +302,19 @@ public function testNonMatchingPsr4EntriesNeverBecomeTheBestMatch(): void $this->assertSame('App\Action', Config::discover($this->root)->namespaceFor('action')); } + public function testNoUsablePsr4EntryNamesEveryMissingDirectory(): void + { + $this->writeComposerJson(['Ghost\\' => 'missing/', 'Other\\' => 'lib']); + + $this->expectException(Exception::class); + $this->expectExceptionMessage( + "no crest.php and no usable psr-4 autoload entry found; " + . "these psr-4 directories do not exist: 'missing', 'lib'" + ); + + Config::discover($this->root); + } + public function testNoUsablePsr4EntryThrows(): void { // composer.json exists but every declared directory is missing, so @@ -297,7 +322,10 @@ public function testNoUsablePsr4EntryThrows(): void $this->writeComposerJson(['Ghost\\' => 'missing/']); $this->expectException(Exception::class); - $this->expectExceptionMessage('no crest.php and no usable psr-4 autoload entry found'); + $this->expectExceptionMessage( + "no crest.php and no usable psr-4 autoload entry found; " + . "these psr-4 directories do not exist: 'missing'" + ); Config::discover($this->root); } From 185fb3088bb64502d1fdcad2982b2228282c65c5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 25 Sep 2026 19:41:14 -0500 Subject: [PATCH 2/2] updating changelog Assisted-by: Claude Code --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188aefc..706c35b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ All notable changes are documented here. The format is based on [Keep a Changelo - `phalcon/talon` moved from `^0.8` to `^0.9`. - `stub:publish` with no name leaves out the `project-*` stubs that `new` renders. They have an effect only in the directory that `new` puts the project into; publish one by name. A `project-*` name goes where `new` reads it - the working directory, or `--directory` - with the `adr` flavor, and needs no `crest.php`. [#8](https://github.com/phalcon/crest/issues/8) - Rendering a stub now fails when a placeholder has no value, and the error names the stub file. A published copy that kept a placeholder crest no longer sends put the raw `{{ name }}` into the generated file. [#8](https://github.com/phalcon/crest/issues/8) +- With no `crest.php`, the error for a `composer.json` whose psr-4 directories do not exist now names those directories: `no crest.php and no usable psr-4 autoload entry found; these psr-4 directories do not exist: 'src'`. Before, the message did not say that a missing directory was the cause. [#18](https://github.com/phalcon/crest/issues/18) ### Fixed