diff --git a/composer.json b/composer.json index b65b5c2..29a2c56 100644 --- a/composer.json +++ b/composer.json @@ -38,8 +38,9 @@ "jolicode/jolinotif": "^3.3", "nikic/php-parser": "^5.7", "php-di/php-di": "^7.1", + "php-parallel-lint/php-parallel-lint": "^1.4", "phpdocumentor/shim": "^3.9", - "phpro/grumphp": "^2.19", + "phpro/grumphp-shim": "^2.19", "phpspec/prophecy": "^1.26", "phpspec/prophecy-phpunit": "^2.5", "phpunit/phpunit": "^12.5", @@ -74,7 +75,7 @@ "allow-plugins": { "ergebnis/composer-normalize": true, "phpdocumentor/shim": true, - "phpro/grumphp": true, + "phpro/grumphp-shim": true, "pyrech/composer-changelogs": true }, "platform": { diff --git a/grumphp.yml b/grumphp.yml index a12833e..b2486ff 100644 --- a/grumphp.yml +++ b/grumphp.yml @@ -1,12 +1,38 @@ grumphp: - stop_on_failure: true + stop_on_failure: true + ignore_unstaged_changes: false + process_timeout: 120 - tasks: - composer_script: - script: 'dev-tools' - triggered_by: ['php'] + ascii: + succeeded: succeeded.txt + failed: failed.txt - testsuites: - git_pre_commit: - tasks: - - composer_script \ No newline at end of file + tasks: + composer_script: + script: 'dev-tools' + triggered_by: ['php'] + + composer: + no_check_lock: true + + phplint: + exclude: ['vendor'] + triggered_by: ['php'] + + jsonlint: ~ + yamllint: ~ + + testsuites: + git_pre_commit: + tasks: + - composer + - phplint + - jsonlint + - yamllint + - composer_script + + parallel: + enabled: true + max_workers: 5 + + hooks_preset: local diff --git a/resources/git-hooks/post-checkout b/resources/git-hooks/post-checkout new file mode 100755 index 0000000..dc4f21c --- /dev/null +++ b/resources/git-hooks/post-checkout @@ -0,0 +1,23 @@ +#!/bin/sh + +# +# DevTools post-checkout hook +# Runs composer update if composer.json was modified after checkout +# + +PREV_HEAD="$1" +NEW_HEAD="$2" +CHECKOUT_TYPE="$3" + +if [ "$CHECKOUT_TYPE" = "1" ]; then + if git diff --quiet --cached "$PREV_HEAD" "$NEW_HEAD" -- composer.json; then + if git diff --quiet "$PREV_HEAD" "$NEW_HEAD" -- composer.json; then + exit 0 + fi + fi + + echo "DevTools: composer.json changed, running composer update..." + (cd "$(git rev-parse --show-toplevel)" && composer update --minimal-changes --no-interaction) || true +fi + +exit 0 diff --git a/resources/git-hooks/post-merge b/resources/git-hooks/post-merge new file mode 100755 index 0000000..aff5926 --- /dev/null +++ b/resources/git-hooks/post-merge @@ -0,0 +1,13 @@ +#!/bin/sh + +# +# DevTools post-merge hook +# Runs composer update if composer.json was modified after merge +# + +if git diff --cached --name-only --diff-filter=M | grep -q "^composer.json$"; then + echo "DevTools: composer.json modified in merge, running composer update..." + (cd "$(git rev-parse --show-toplevel)" && composer update --minimal-changes --no-interaction) || true +fi + +exit 0 diff --git a/src/Console/Command/SyncCommand.php b/src/Console/Command/SyncCommand.php index b80de23..cbe259b 100644 --- a/src/Console/Command/SyncCommand.php +++ b/src/Console/Command/SyncCommand.php @@ -64,6 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->runCommand('gitattributes', $output); $this->runCommand('skills', $output); $this->runCommand('license', $output); + $this->copyGitHooks($output); return self::SUCCESS; } @@ -95,7 +96,7 @@ private function updateComposerJson(): void $extra = [ 'grumphp' => [ 'config-default-path' => Path::makeRelative( - \dirname(__DIR__, 2) . '/grumphp.yml', + \dirname(__DIR__, 3) . '/grumphp.yml', $this->getCurrentWorkingDirectory(), ), ], @@ -223,4 +224,33 @@ private function getGitRepositoryUrl(): string return trim($process->getOutput()); } + + /** + * Copies git hooks from resources to .git/hooks for vendor synchronization. + * + * This method copies post-checkout and post-merge hooks from the package resources to .git/hooks, + * ensuring vendor dependencies stay synchronized when switching branches. + * + * @param OutputInterface $output the output interface + * + * @return void + */ + private function copyGitHooks(OutputInterface $output): void + { + $hooksDir = parent::getDevToolsFile('resources/git-hooks'); + $targetDir = Path::join($this->getCurrentWorkingDirectory(), '.git', 'hooks'); + + $finder = Finder::create() + ->files() + ->in($hooksDir); + + foreach ($finder as $file) { + $targetPath = Path::join($targetDir, $file->getFilename()); + + $this->filesystem->copy($file->getRealPath(), $targetPath, true); + $this->filesystem->chmod($targetPath, 755, 0o755); + + $output->writeln(\sprintf('Installed %s hook', $file->getFilename())); + } + } } diff --git a/tests/Console/Command/SyncCommandTest.php b/tests/Console/Command/SyncCommandTest.php index 4772c28..e564be5 100644 --- a/tests/Console/Command/SyncCommandTest.php +++ b/tests/Console/Command/SyncCommandTest.php @@ -30,9 +30,7 @@ use FastForward\DevTools\GitIgnore\Reader; use FastForward\DevTools\GitIgnore\Writer; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; #[CoversClass(SyncCommand::class)] @@ -81,19 +79,4 @@ protected function getCommandHelp(): string { return 'This command adds or updates dev-tools scripts in composer.json, copies reusable GitHub Actions workflows, ensures .editorconfig is present and up to date, and manages .gitattributes export-ignore rules.'; } - - /** - * @return void - */ - #[Test] - public function executeWillReturnSuccessAndWriteInfo(): void - { - $this->filesystem->exists(Argument::any())->willReturn(true); - $this->filesystem->dumpFile(Argument::cetera())->shouldBeCalled(); - - $this->output->writeln(Argument::type('string')) - ->shouldBeCalled(); - - self::assertSame(SyncCommand::SUCCESS, $this->invokeExecute()); - } }