From c8df8d6a85eca990072a0ee550c77bb171030760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 29 Apr 2026 23:50:04 -0300 Subject: [PATCH 1/4] [path] Complete runtime-aware binary fallbacks for remaining commands --- CHANGELOG.md | 1 + src/Console/Command/DocsCommand.php | 3 ++- src/Console/Command/MetricsCommand.php | 7 ++++--- src/Console/Command/TestsCommand.php | 3 ++- src/Console/Command/WikiCommand.php | 3 ++- tests/Console/Command/DocsCommandTest.php | 6 +++++- tests/Console/Command/MetricsCommandTest.php | 7 ++++++- tests/Console/Command/TestsCommandTest.php | 5 +++++ tests/Console/Command/WikiCommandTest.php | 7 ++++++- tests/Path/DevToolsPathResolverTest.php | 3 +++ 10 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dfed9aee8..6e01a59292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Keep global `dev-tools:sync` runs machine-independent by removing only deprecated DevTools-managed Composer GrumPHP default-path metadata while wiring packaged Git hooks to prefer a project-local `grumphp.yml` and otherwise use the active packaged DevTools config path resolved at sync time (#288) +- Complete global runtime binary fallback support for phpdoc, phpunit, and phpmetrics commands (#297) ## [1.24.2] - 2026-04-30 diff --git a/src/Console/Command/DocsCommand.php b/src/Console/Command/DocsCommand.php index 5cc9a460b9..5bcdfd47d8 100644 --- a/src/Console/Command/DocsCommand.php +++ b/src/Console/Command/DocsCommand.php @@ -25,6 +25,7 @@ use FastForward\DevTools\Console\Input\HasJsonOption; use Twig\Environment; use FastForward\DevTools\Filesystem\FilesystemInterface; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; @@ -167,7 +168,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $processBuilder = $processBuilder->withArgument('--no-progress'); } - $phpdoc = $processBuilder->build('vendor/bin/phpdoc'); + $phpdoc = $processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('phpdoc')]); $this->processQueue->add(process: $phpdoc, label: 'Generating API Docs with phpDocumentor'); diff --git a/src/Console/Command/MetricsCommand.php b/src/Console/Command/MetricsCommand.php index 6b7dc54f61..972b47358b 100644 --- a/src/Console/Command/MetricsCommand.php +++ b/src/Console/Command/MetricsCommand.php @@ -21,6 +21,7 @@ use FastForward\DevTools\Console\Command\Traits\LogsCommandResults; use FastForward\DevTools\Console\Input\HasJsonOption; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; @@ -45,9 +46,9 @@ final class MetricsCommand extends Command use LogsCommandResults; /** - * @var string the bundled PhpMetrics binary path relative to the consumer root + * @var string the PhpMetrics binary name resolved through the runtime-aware tooling lookup */ - private const string BINARY = 'vendor/bin/phpmetrics'; + private const string BINARY = 'phpmetrics'; /** * @var int the PHP error reporting mask that suppresses deprecations emitted by PhpMetrics internals @@ -161,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int \PHP_BINARY, '-derror_reporting=' . self::PHP_ERROR_REPORTING, '-ddefault_socket_timeout=' . self::PHP_DEFAULT_SOCKET_TIMEOUT, - self::BINARY, + DevToolsPathResolver::getPreferredToolBinaryPath(self::BINARY), ]), label: 'Generating Metrics with PhpMetrics', ); diff --git a/src/Console/Command/TestsCommand.php b/src/Console/Command/TestsCommand.php index a7f72e4e9b..2ce856a7cb 100644 --- a/src/Console/Command/TestsCommand.php +++ b/src/Console/Command/TestsCommand.php @@ -24,6 +24,7 @@ use FastForward\DevTools\Console\Input\HasJsonOption; use FastForward\DevTools\Composer\Json\ComposerJsonInterface; use FastForward\DevTools\Filesystem\FilesystemInterface; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\PhpUnit\Bootstrap\BootstrapShimGenerator; use FastForward\DevTools\PhpUnit\Coverage\CoverageSummaryLoaderInterface; use FastForward\DevTools\Process\ProcessBuilderInterface; @@ -209,7 +210,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->processQueue->add( process: $processBuilder ->withArgument($input->getArgument('path')) - ->build('vendor/bin/phpunit'), + ->build([DevToolsPathResolver::getPreferredToolBinaryPath('phpunit')]), label: 'Running PHPUnit Tests', ); diff --git a/src/Console/Command/WikiCommand.php b/src/Console/Command/WikiCommand.php index 01d7394022..2ec871b51a 100644 --- a/src/Console/Command/WikiCommand.php +++ b/src/Console/Command/WikiCommand.php @@ -25,6 +25,7 @@ use FastForward\DevTools\Console\Input\HasJsonOption; use FastForward\DevTools\Filesystem\FilesystemInterface; use FastForward\DevTools\Git\GitClientInterface; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; @@ -157,7 +158,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->processQueue->add( - process: $processBuilder->build('vendor/bin/phpdoc'), + process: $processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('phpdoc')]), label: 'Generating Wiki with phpDocumentor', ); diff --git a/tests/Console/Command/DocsCommandTest.php b/tests/Console/Command/DocsCommandTest.php index 1ea196cc1d..ac7f1e424f 100644 --- a/tests/Console/Command/DocsCommandTest.php +++ b/tests/Console/Command/DocsCommandTest.php @@ -23,9 +23,11 @@ use FastForward\DevTools\Console\Command\Traits\LogsCommandResults; use FastForward\DevTools\Console\Command\DocsCommand; use FastForward\DevTools\Filesystem\FilesystemInterface; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; +use FastForward\DevTools\Path\WorkingProjectPathResolver; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; @@ -43,7 +45,9 @@ use Twig\Environment; #[CoversClass(DocsCommand::class)] +#[UsesClass(DevToolsPathResolver::class)] #[UsesClass(ManagedWorkspace::class)] +#[UsesClass(WorkingProjectPathResolver::class)] #[UsesTrait(LogsCommandResults::class)] final class DocsCommandTest extends TestCase { @@ -133,7 +137,7 @@ protected function setUp(): void $this->processBuilder->withArgument(Argument::any(), Argument::any())->willReturn( $this->processBuilder->reveal() ); - $this->processBuilder->build('vendor/bin/phpdoc') + $this->processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('phpdoc')]) ->willReturn($this->process->reveal()); $this->command = new DocsCommand( diff --git a/tests/Console/Command/MetricsCommandTest.php b/tests/Console/Command/MetricsCommandTest.php index 3e2a91cb12..6f7fc68075 100644 --- a/tests/Console/Command/MetricsCommandTest.php +++ b/tests/Console/Command/MetricsCommandTest.php @@ -21,9 +21,11 @@ use FastForward\DevTools\Console\Command\MetricsCommand; use FastForward\DevTools\Console\Command\Traits\LogsCommandResults; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; +use FastForward\DevTools\Path\WorkingProjectPathResolver; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; @@ -42,7 +44,9 @@ use function Safe\putenv; #[CoversClass(MetricsCommand::class)] +#[UsesClass(DevToolsPathResolver::class)] #[UsesClass(ManagedWorkspace::class)] +#[UsesClass(WorkingProjectPathResolver::class)] #[UsesTrait(LogsCommandResults::class)] final class MetricsCommandTest extends TestCase { @@ -99,7 +103,8 @@ protected function setUp(): void $this->processBuilder->build(Argument::that(static fn(array $command): bool => \PHP_BINARY === $command[0] && str_starts_with((string) $command[1], '-derror_reporting=') && '-ddefault_socket_timeout=1' === $command[2] - && 'vendor/bin/phpmetrics' === $command[3]))->willReturn($this->process->reveal()); + && DevToolsPathResolver::getPreferredToolBinaryPath('phpmetrics') === $command[3])) + ->willReturn($this->process->reveal()); $this->command = new MetricsCommand( $this->processBuilder->reveal(), $this->processQueue->reveal(), diff --git a/tests/Console/Command/TestsCommandTest.php b/tests/Console/Command/TestsCommandTest.php index 791a63bffa..12a8a2a5ea 100644 --- a/tests/Console/Command/TestsCommandTest.php +++ b/tests/Console/Command/TestsCommandTest.php @@ -30,6 +30,7 @@ use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; use FastForward\DevTools\Path\DevToolsPathResolver; +use FastForward\DevTools\Path\WorkingProjectPathResolver; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; @@ -54,6 +55,7 @@ #[UsesClass(DevToolsPathResolver::class)] #[UsesClass(ProcessBuilder::class)] #[UsesClass(ManagedWorkspace::class)] +#[UsesClass(WorkingProjectPathResolver::class)] #[UsesTrait(LogsCommandResults::class)] final class TestsCommandTest extends TestCase { @@ -147,6 +149,9 @@ public function executeWillRunPhpUnitProcessWithConfigFile(): void ) && str_contains( $process->getCommandLine(), '--bootstrap=' . $generatedBootstrapPath, + ) && str_contains( + $process->getCommandLine(), + DevToolsPathResolver::getPreferredToolBinaryPath('phpunit'), ) && str_contains($process->getCommandLine(), '--cache-result') && str_contains( $process->getCommandLine(), '--cache-directory=' . getcwd() . '/.dev-tools/cache/phpunit', diff --git a/tests/Console/Command/WikiCommandTest.php b/tests/Console/Command/WikiCommandTest.php index 69030c563d..927ac4e440 100644 --- a/tests/Console/Command/WikiCommandTest.php +++ b/tests/Console/Command/WikiCommandTest.php @@ -24,9 +24,11 @@ use FastForward\DevTools\Console\Command\WikiCommand; use FastForward\DevTools\Filesystem\FilesystemInterface; use FastForward\DevTools\Git\GitClientInterface; +use FastForward\DevTools\Path\DevToolsPathResolver; use FastForward\DevTools\Process\ProcessBuilderInterface; use FastForward\DevTools\Process\ProcessQueueInterface; use FastForward\DevTools\Path\ManagedWorkspace; +use FastForward\DevTools\Path\WorkingProjectPathResolver; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; @@ -42,7 +44,9 @@ use Symfony\Component\Process\Process; #[CoversClass(WikiCommand::class)] +#[UsesClass(DevToolsPathResolver::class)] #[UsesClass(ManagedWorkspace::class)] +#[UsesClass(WorkingProjectPathResolver::class)] #[UsesTrait(LogsCommandResults::class)] final class WikiCommandTest extends TestCase { @@ -107,7 +111,8 @@ protected function setUp(): void $this->processBuilder->withArgument(Argument::any(), Argument::any())->willReturn( $this->processBuilder->reveal() ); - $this->processBuilder->build(Argument::any())->willReturn($this->process->reveal()); + $this->processBuilder->build([DevToolsPathResolver::getPreferredToolBinaryPath('phpdoc')]) + ->willReturn($this->process->reveal()); $this->command = new WikiCommand( $this->processBuilder->reveal(), diff --git a/tests/Path/DevToolsPathResolverTest.php b/tests/Path/DevToolsPathResolverTest.php index 3d78af5a28..c4a9208816 100644 --- a/tests/Path/DevToolsPathResolverTest.php +++ b/tests/Path/DevToolsPathResolverTest.php @@ -112,6 +112,9 @@ public function itWillResolveRuntimeAutoloadPathsForRepositoryAndDependencyInsta */ #[Test] #[TestWith(['php-cs-fixer'])] + #[TestWith(['phpdoc'])] + #[TestWith(['phpmetrics'])] + #[TestWith(['phpunit'])] #[TestWith(['rector'])] #[TestWith(['ecs'])] #[TestWith(['jack'])] From f3137fc489ce8660d030eeaf0bca490975f87c5e Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 02:52:55 +0000 Subject: [PATCH 2/4] Update wiki submodule pointer for PR #298 --- .github/wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/wiki b/.github/wiki index 02bb3c4c9c..2cb2cf22d3 160000 --- a/.github/wiki +++ b/.github/wiki @@ -1 +1 @@ -Subproject commit 02bb3c4c9cc8df45bddda258c4f0ee7bbd29928c +Subproject commit 2cb2cf22d32e01e16c212c3d2d6156903950c560 From e55f89019a0ddc61d53908a4e53251eb953d165f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 30 Apr 2026 00:03:59 -0300 Subject: [PATCH 3/4] [path] Fix phpDocumentor template runtime fallbacks --- CHANGELOG.md | 2 +- src/Console/Command/DocsCommand.php | 14 ++++- src/Console/Command/WikiCommand.php | 7 ++- src/Path/DevToolsPathResolver.php | 64 +++++++++++++++++++++++ tests/Console/Command/DocsCommandTest.php | 9 +++- tests/Console/Command/WikiCommandTest.php | 6 +++ tests/Path/DevToolsPathResolverTest.php | 59 ++++++++++++++++++++- 7 files changed, 155 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e01a59292..118212e3ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Keep global `dev-tools:sync` runs machine-independent by removing only deprecated DevTools-managed Composer GrumPHP default-path metadata while wiring packaged Git hooks to prefer a project-local `grumphp.yml` and otherwise use the active packaged DevTools config path resolved at sync time (#288) -- Complete global runtime binary fallback support for phpdoc, phpunit, and phpmetrics commands (#297) +- Complete global runtime fallback support for phpdoc, phpunit, and phpmetrics commands, including packaged phpDocumentor templates used by `docs` and `wiki` (#297) ## [1.24.2] - 2026-04-30 diff --git a/src/Console/Command/DocsCommand.php b/src/Console/Command/DocsCommand.php index 5bcdfd47d8..9ad95fa9cf 100644 --- a/src/Console/Command/DocsCommand.php +++ b/src/Console/Command/DocsCommand.php @@ -58,6 +58,11 @@ final class DocsCommand extends Command use HasJsonOption; use LogsCommandResults; + /** + * @var string the default phpDocumentor template path relative to the consumer project + */ + private const string DEFAULT_TEMPLATE = 'vendor/fast-forward/phpdoc-bootstrap-template'; + /** * Creates a new DocsCommand instance. * @@ -115,7 +120,7 @@ protected function configure(): void name: 'template', mode: InputOption::VALUE_OPTIONAL, description: 'Path to the template directory for the generated HTML documentation.', - default: 'vendor/fast-forward/phpdoc-bootstrap-template', + default: self::DEFAULT_TEMPLATE, ); } @@ -137,6 +142,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $source = $this->filesystem->getAbsolutePath($input->getOption('source')); $target = $this->filesystem->getAbsolutePath($input->getOption('target')); $cacheDir = $this->filesystem->getAbsolutePath($input->getOption('cache-dir')); + $template = (string) $input->getOption('template'); + + if (self::DEFAULT_TEMPLATE === $template) { + $template = DevToolsPathResolver::getPreferredVendorPath(self::DEFAULT_TEMPLATE); + } $this->logger->info('Generating API documentation...', [ 'input' => $input, @@ -151,7 +161,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $config = $this->createPhpDocumentorConfig( source: $source, target: $target, - template: $input->getOption('template'), + template: $template, cacheDir: $cacheEnabled ? $cacheDir : sys_get_temp_dir(), ); diff --git a/src/Console/Command/WikiCommand.php b/src/Console/Command/WikiCommand.php index 2ec871b51a..e78cc39505 100644 --- a/src/Console/Command/WikiCommand.php +++ b/src/Console/Command/WikiCommand.php @@ -55,6 +55,11 @@ final class WikiCommand extends Command use HasJsonOption; use LogsCommandResults; + /** + * @var string the default phpDocumentor Markdown template path relative to the consumer project + */ + private const string DEFAULT_TEMPLATE = 'vendor/saggre/phpdocumentor-markdown/themes/markdown'; + /** * Creates a new WikiCommand instance. * @@ -139,7 +144,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $processBuilder = $this->processBuilder ->withArgument('--ansi') ->withArgument('--visibility', 'public,protected') - ->withArgument('--template', 'vendor/saggre/phpdocumentor-markdown/themes/markdown') + ->withArgument('--template', DevToolsPathResolver::getPreferredVendorPath(self::DEFAULT_TEMPLATE)) ->withArgument('--title', $this->composer->getDescription()) ->withArgument('--target', $target); diff --git a/src/Path/DevToolsPathResolver.php b/src/Path/DevToolsPathResolver.php index a48e1dd142..021c280afa 100644 --- a/src/Path/DevToolsPathResolver.php +++ b/src/Path/DevToolsPathResolver.php @@ -128,6 +128,27 @@ public static function getRuntimeToolBinaryPath(string $binary, string $packageP return Path::join($packagePath, 'vendor', 'bin', $binary); } + /** + * Returns the active Composer vendor path for the current DevTools installation mode. + * + * Relative vendor paths MAY be passed either with or without a leading + * `vendor/` prefix. + * + * @param string $path the vendor-relative path to resolve + * @param string $packagePath an optional package root path; defaults to the current package root + */ + public static function getRuntimeVendorPath(string $path, string $packagePath = ''): string + { + $packagePath = Path::canonicalize('' === $packagePath ? self::getPackagePath() : $packagePath); + $vendorPath = self::normalizeVendorRelativePath($path); + + if (self::isInstalledAsDependency($packagePath)) { + return Path::canonicalize(Path::join($packagePath, '..', '..', $vendorPath)); + } + + return Path::join($packagePath, 'vendor', $vendorPath); + } + /** * Returns the preferred tooling binary path for the active project and DevTools runtime. * @@ -154,6 +175,33 @@ public static function getPreferredToolBinaryPath( return self::getRuntimeToolBinaryPath($binary, $packagePath); } + /** + * Returns the preferred Composer vendor path for the active project and DevTools runtime. + * + * Consumer projects SHOULD take precedence when they provide the requested + * vendor path locally. If the path is absent locally, the method MUST fall + * back to the active DevTools runtime vendor path. + * + * @param string $path the vendor-relative path to resolve + * @param string $projectPath an optional project root path; defaults to the working project root + * @param string $packagePath an optional package root path; defaults to the current package root + */ + public static function getPreferredVendorPath( + string $path, + string $projectPath = '', + string $packagePath = '', + ): string { + $projectPath = '' === $projectPath ? WorkingProjectPathResolver::getProjectPath() : $projectPath; + $vendorPath = self::normalizeVendorRelativePath($path); + $projectVendorPath = Path::join($projectPath, 'vendor', $vendorPath); + + if (file_exists($projectVendorPath)) { + return $projectVendorPath; + } + + return self::getRuntimeVendorPath($vendorPath, $packagePath); + } + /** * Detects whether the provided path belongs to an installed vendor copy of DevTools. * @@ -175,4 +223,20 @@ public static function isRepositoryCheckout(string $packagePath = ''): bool { return ! self::isInstalledAsDependency($packagePath); } + + /** + * Normalizes a path relative to the Composer vendor root. + * + * @param string $path the vendor-relative path to normalize + */ + private static function normalizeVendorRelativePath(string $path): string + { + $path = Path::canonicalize($path); + + if (str_starts_with($path, 'vendor/')) { + return substr($path, 7); + } + + return $path; + } } diff --git a/tests/Console/Command/DocsCommandTest.php b/tests/Console/Command/DocsCommandTest.php index ac7f1e424f..6840e2b118 100644 --- a/tests/Console/Command/DocsCommandTest.php +++ b/tests/Console/Command/DocsCommandTest.php @@ -132,7 +132,14 @@ protected function setUp(): void ]); $this->composer->getName() ->willReturn('fast-forward/dev-tools'); - $this->renderer->render('phpdocumentor.xml', Argument::type('array'))->willReturn(''); + $this->renderer->render( + 'phpdocumentor.xml', + Argument::that( + static fn(array $context): bool => DevToolsPathResolver::getPreferredVendorPath( + 'vendor/fast-forward/phpdoc-bootstrap-template' + ) === $context['template'] + ) + )->willReturn(''); $this->processBuilder->withArgument(Argument::any())->willReturn($this->processBuilder->reveal()); $this->processBuilder->withArgument(Argument::any(), Argument::any())->willReturn( $this->processBuilder->reveal() diff --git a/tests/Console/Command/WikiCommandTest.php b/tests/Console/Command/WikiCommandTest.php index 927ac4e440..80f742e548 100644 --- a/tests/Console/Command/WikiCommandTest.php +++ b/tests/Console/Command/WikiCommandTest.php @@ -130,6 +130,12 @@ protected function setUp(): void #[Test] public function executeWillReturnSuccessWhenProcessQueueSucceeds(): void { + $this->processBuilder->withArgument( + '--template', + DevToolsPathResolver::getPreferredVendorPath('vendor/saggre/phpdocumentor-markdown/themes/markdown') + ) + ->willReturn($this->processBuilder->reveal()) + ->shouldBeCalled(); $this->processBuilder->withArgument( '--cache-folder', ManagedWorkspace::getCacheDirectory(ManagedWorkspace::PHPDOC) diff --git a/tests/Path/DevToolsPathResolverTest.php b/tests/Path/DevToolsPathResolverTest.php index c4a9208816..c82ff3f9f2 100644 --- a/tests/Path/DevToolsPathResolverTest.php +++ b/tests/Path/DevToolsPathResolverTest.php @@ -51,6 +51,10 @@ public function itWillExposeCanonicalPackagePaths(): void \dirname(__DIR__, 2) . '/vendor/bin/ecs', DevToolsPathResolver::getRuntimeToolBinaryPath('ecs') ); + self::assertSame( + \dirname(__DIR__, 2) . '/vendor/fast-forward/phpdoc-bootstrap-template', + DevToolsPathResolver::getRuntimeVendorPath('vendor/fast-forward/phpdoc-bootstrap-template') + ); self::assertSame( \dirname(__DIR__, 2) . '/resources/phpdocumentor.xml', DevToolsPathResolver::getResourcesPath('phpdocumentor.xml') @@ -93,7 +97,7 @@ public function itWillDetectWhetherDevToolsRunsFromVendorOrRepositoryCheckout(): * @return void */ #[Test] - public function itWillResolveRuntimeAutoloadPathsForRepositoryAndDependencyInstalls(): void + public function itWillResolveRuntimeAutoloadAndVendorPathsForRepositoryAndDependencyInstalls(): void { self::assertSame( '/workspaces/dev-tools/vendor/autoload.php', @@ -103,6 +107,13 @@ public function itWillResolveRuntimeAutoloadPathsForRepositoryAndDependencyInsta '/workspaces/project/vendor/autoload.php', DevToolsPathResolver::getRuntimeAutoloadPath('/workspaces/project/vendor/fast-forward/dev-tools') ); + self::assertSame( + '/workspaces/project/vendor/saggre/phpdocumentor-markdown/themes/markdown', + DevToolsPathResolver::getRuntimeVendorPath( + 'vendor/saggre/phpdocumentor-markdown/themes/markdown', + '/workspaces/project/vendor/fast-forward/dev-tools' + ) + ); } /** @@ -175,4 +186,50 @@ public function itWillFallbackToRuntimeToolBinariesWhenTheProjectDoesNotProvideT ) ); } + + /** + * @return void + */ + #[Test] + public function itWillPreferProjectVendorPathsWhenTheyExist(): void + { + $projectPath = sys_get_temp_dir() . '/dev-tools-vendor-path-resolver-' . bin2hex(random_bytes(4)); + $vendorPath = $projectPath . '/vendor/saggre/phpdocumentor-markdown/themes/markdown'; + + mkdir($vendorPath, 0o777, true); + + try { + self::assertSame( + $vendorPath, + DevToolsPathResolver::getPreferredVendorPath( + 'vendor/saggre/phpdocumentor-markdown/themes/markdown', + $projectPath, + '/Users/example/.composer/vendor/fast-forward/dev-tools' + ) + ); + } finally { + rmdir($vendorPath); + rmdir($projectPath . '/vendor/saggre/phpdocumentor-markdown/themes'); + rmdir($projectPath . '/vendor/saggre/phpdocumentor-markdown'); + rmdir($projectPath . '/vendor/saggre'); + rmdir($projectPath . '/vendor'); + rmdir($projectPath); + } + } + + /** + * @return void + */ + #[Test] + public function itWillFallbackToRuntimeVendorPathsWhenTheProjectDoesNotProvideThem(): void + { + self::assertSame( + '/Users/example/.composer/vendor/fast-forward/phpdoc-bootstrap-template', + DevToolsPathResolver::getPreferredVendorPath( + 'vendor/fast-forward/phpdoc-bootstrap-template', + '/workspaces/project', + '/Users/example/.composer/vendor/fast-forward/dev-tools' + ) + ); + } } From 8c8e493875e6d69e17d0a33445d82619a69f70aa Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 03:07:17 +0000 Subject: [PATCH 4/4] Update wiki submodule pointer for PR #298 --- .github/wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/wiki b/.github/wiki index 02bb3c4c9c..bce2ecf64a 160000 --- a/.github/wiki +++ b/.github/wiki @@ -1 +1 @@ -Subproject commit 02bb3c4c9cc8df45bddda258c4f0ee7bbd29928c +Subproject commit bce2ecf64a026c396a0c3a2dc9b37eb837abdba2