diff --git a/config/git-hooks.php b/config/git-hooks.php index 3109e58..97c9ef0 100644 --- a/config/git-hooks.php +++ b/config/git-hooks.php @@ -164,7 +164,8 @@ | The following variables are used to store the paths to bin executables for | various code analyzer dependencies used in your Laravel application. | This configuration node allows you to set up the paths for these executables. - | Here you can also specify the path to the configuration files they use to customize their behavior + | Here you can also specify the path to the configuration files they use to customize their behavior. + | Each analyzer can be configured to run inside Docker on a specific container. | */ 'code_analyzers' => [ @@ -173,34 +174,46 @@ 'config' => env('LARAVEL_PINT_CONFIG'), 'preset' => env('LARAVEL_PINT_PRESET', 'laravel'), 'file_extensions' => env('LARAVEL_PINT_FILE_EXTENSIONS', '/\.php$/'), + 'run_in_docker' => env('LARAVEL_PINT_RUN_IN_DOCKER', false), + 'docker_container' => env('LARAVEL_PINT_DOCKER_CONTAINER', ''), ], 'php_code_sniffer' => [ 'phpcs_path' => env('PHPCS_PATH', 'vendor/bin/phpcs'), 'phpcbf_path' => env('PHPCBF_PATH', 'vendor/bin/phpcbf'), 'config' => env('PHPCS_STANDARD_CONFIG', 'phpcs.xml'), 'file_extensions' => env('PHPCS_FILE_EXTENSIONS', '/\.php$/'), + 'run_in_docker' => env('LARAVEL_PHPCS_RUN_IN_DOCKER', false), + 'docker_container' => env('LARAVEL_PHPCS_DOCKER_CONTAINER', ''), ], 'larastan' => [ 'path' => env('LARASTAN_PATH', 'vendor/bin/phpstan'), 'config' => env('LARASTAN_CONFIG', 'phpstan.neon'), 'additional_params' => env('LARASTAN_ADDITIONAL_PARAMS', '--xdebug'), + 'run_in_docker' => env('LARAVEL_LARASTAN_RUN_IN_DOCKER', false), + 'docker_container' => env('LARAVEL_LARASTAN_DOCKER_CONTAINER', ''), ], 'blade_formatter' => [ 'path' => env('BLADE_FORMATTER_PATH', 'node_modules/.bin/blade-formatter'), 'config' => env('BLADE_FORMATTER_CONFIG', '.bladeformatterrc.json'), 'file_extensions' => env('BLADE_FORMATTER_FILE_EXTENSIONS', '/\.blade\.php$/'), + 'run_in_docker' => env('BLADE_FORMATTER_RUN_IN_DOCKER', false), + 'docker_container' => env('BLADE_FORMATTER_DOCKER_CONTAINER', ''), ], 'prettier' => [ 'path' => env('PRETTIER_PATH', 'node_modules/.bin/prettier'), 'config' => env('PRETTIER_CONFIG', '.prettierrc.json'), 'additional_params' => env('PRETTIER_ADDITIONAL_PARAMS', ''), 'file_extensions' => env('PRETTIER_FILE_EXTENSIONS', '/\.(jsx?|tsx?|vue)$/'), + 'run_in_docker' => env('PRETTIER_RUN_IN_DOCKER', false), + 'docker_container' => env('PRETTIER_DOCKER_CONTAINER', ''), ], 'eslint' => [ 'path' => env('ESLINT_PATH', 'node_modules/.bin/eslint'), 'config' => env('ESLINT_CONFIG', '.eslintrc.js'), 'additional_params' => env('ESLINT_ADDITIONAL_PARAMS', ''), 'file_extensions' => env('ESLINT_FILE_EXTENSIONS', '/\.(jsx?|tsx?|vue)$/'), + 'run_in_docker' => env('ESLINT_RUN_IN_DOCKER', false), + 'docker_container' => env('ESLINT_DOCKER_CONTAINER', ''), ], ], @@ -221,4 +234,60 @@ | */ 'artisan_path' => base_path('artisan'), + + /* + |-------------------------------------------------------------------------- + | Output errors + |-------------------------------------------------------------------------- + | + | This configuration option allows you output any errors encountered + | during execution directly for easy debug. + | + */ + 'output_errors' => false, + + /* + |-------------------------------------------------------------------------- + | Automatically fix errors + |-------------------------------------------------------------------------- + | + | This configuration option allows you to configure the git hooks to + | automatically run the fixer without any CLI prompt. + | + */ + 'automatically_fix_errors' => false, + + /* + |-------------------------------------------------------------------------- + | Automatically re-run analyzer after autofix + |-------------------------------------------------------------------------- + | + | This configuration option allows you to configure the git hooks to + | automatically re-run the analyzer command after autofix. + | The git hooks will not fail in case the re-run is succesful. + | + */ + 'rerun_analyzer_after_autofix' => false, + + /* + |-------------------------------------------------------------------------- + | Stop at first analyzer failure + |-------------------------------------------------------------------------- + | + | This configuration option allows you to configure the git hooks to + | stop (or not) at the first analyzer failure encountered. + | + */ + 'stop_at_first_analyzer_failure' => true, + + /* + |-------------------------------------------------------------------------- + | Debug commands + |-------------------------------------------------------------------------- + | + | This configuration option allows you to configure the git hooks to + | display the commands that are executed (usually for debug purpose). + | + */ + 'debug_commands' => false, ]; diff --git a/src/Console/Commands/Hooks/BaseCodeAnalyzerPreCommitHook.php b/src/Console/Commands/Hooks/BaseCodeAnalyzerPreCommitHook.php index 481e986..5bd9072 100644 --- a/src/Console/Commands/Hooks/BaseCodeAnalyzerPreCommitHook.php +++ b/src/Console/Commands/Hooks/BaseCodeAnalyzerPreCommitHook.php @@ -57,6 +57,20 @@ abstract class BaseCodeAnalyzerPreCommitHook * @var array */ protected $filesBadlyFormattedPaths = []; + + /** + * Run tool in docker + * + * @var bool + */ + protected $runInDocker = false; + + /** + * Docker container on which to run + * + * @var string + */ + protected $dockerContainer = ''; public function __construct() { @@ -109,9 +123,15 @@ protected function analizeCommittedFiles($commitFiles) } $filePath = $file->getFilePath(); - $command = $this->analyzerCommand().' '.$filePath; + $command = $this->dockerCommand($this->analyzerCommand().' '.$filePath); - $isProperlyFormatted = $this->runCommands($command)->isSuccessful(); + $process = $this->runCommands($command); + + if (config('git-hooks.debug_commands')) { + $this->command->getOutput()->write(PHP_EOL . ' DEBUG Executed command: ' . $process->getCommandLine() . PHP_EOL); + } + + $isProperlyFormatted = $process->isSuccessful(); if (! $isProperlyFormatted) { if (empty($this->filesBadlyFormattedPaths)) { @@ -122,6 +142,11 @@ protected function analizeCommittedFiles($commitFiles) sprintf(' %s Failed: %s', $this->getName(), $filePath) ); $this->filesBadlyFormattedPaths[] = $filePath; + + if (config('git-hooks.output_errors')) { + $this->command->newLine(); + $this->command->getOutput()->write($process->getOutput()); + } } } @@ -217,14 +242,29 @@ protected function suggestAutoFixOrExit(): bool { $hasFixerCommand = ! empty($this->fixerCommand()); - if ($hasFixerCommand && Terminal::hasSttyAvailable() && - $this->command->confirm('Would you like to attempt to correct files automagically?') && - $this->autoFixFiles() - ) { - return true; + if ($hasFixerCommand) { + if (config('git-hooks.automatically_fix_errors')) { + $this->command->getOutput()->writeln( + sprintf(' AUTOFIX %s Running Autofix', $this->getName()) + ); + if ($this->autoFixFiles()) { + return true; + } + } else { + if (Terminal::hasSttyAvailable() && + $this->command->confirm('Would you like to attempt to correct files automagically?') && + $this->autoFixFiles() + ) { + return true; + } + } } - throw new HookFailException(); + if (config('git-hooks.stop_at_first_analyzer_failure')) { + throw new HookFailException(); + } + + return false; } /** @@ -238,7 +278,15 @@ protected function suggestAutoFixOrExit(): bool private function autoFixFiles(): bool { foreach ($this->filesBadlyFormattedPaths as $key => $filePath) { - $wasProperlyFixed = $this->runCommands($this->fixerCommand().' '.$filePath)->isSuccessful(); + $fixerCommand = $this->dockerCommand($this->fixerCommand().' '.$filePath); + $process = $this->runCommands($fixerCommand); + + if (config('git-hooks.rerun_analyzer_after_autofix')) { + $command = $this->dockerCommand($this->analyzerCommand().' '.$filePath); + $process = $this->runCommands($command); + } + + $wasProperlyFixed = $process->isSuccessful(); if ($wasProperlyFixed) { $this->runCommands('git add '.$filePath); @@ -250,6 +298,11 @@ private function autoFixFiles(): bool $this->command->getOutput()->writeln( sprintf(' %s Autofix Failed: %s', $this->getName(), $filePath) ); + + if (config('git-hooks.output_errors')) { + $this->command->newLine(); + $this->command->getOutput()->write($process->getOutput()); + } } return empty($this->filesBadlyFormattedPaths); @@ -303,4 +356,44 @@ public function getFixerExecutable(): string { return $this->fixerExecutable; } + + /** + * @return BaseCodeAnalyzerPreCommitHook + */ + public function setRunInDocker($runInDocker) + { + $this->runInDocker = (bool)$runInDocker; + + return $this; + } + + public function getRunInDocker(): bool + { + return $this->runInDocker; + } + + /** + * @param string $dockerContainer + * @return BaseCodeAnalyzerPreCommitHook + */ + public function setDockerContainer($dockerContainer) + { + $this->dockerContainer = $dockerContainer; + + return $this; + } + + public function getDockerContainer(): string + { + return $this->dockerContainer; + } + + private function dockerCommand(string $command): string + { + if (!$this->runInDocker || empty($this->dockerContainer)) { + return $command; + } + + return 'docker exec ' . escapeshellarg($this->dockerContainer) . ' sh -c ' . escapeshellarg($command); + } } diff --git a/src/Console/Commands/Hooks/BladeFormatterPreCommitHook.php b/src/Console/Commands/Hooks/BladeFormatterPreCommitHook.php index 868ad6a..1c6228d 100644 --- a/src/Console/Commands/Hooks/BladeFormatterPreCommitHook.php +++ b/src/Console/Commands/Hooks/BladeFormatterPreCommitHook.php @@ -33,6 +33,8 @@ public function handle(ChangedFiles $files, Closure $next) return $this->setFileExtensions(config('git-hooks.code_analyzers.blade_formatter.file_extensions')) ->setAnalyzerExecutable(config('git-hooks.code_analyzers.blade_formatter.path'), true) + ->setRunInDocker(config('git-hooks.code_analyzers.blade_formatter.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.blade_formatter.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/src/Console/Commands/Hooks/ESLintPreCommitHook.php b/src/Console/Commands/Hooks/ESLintPreCommitHook.php index 1021e37..177b9df 100644 --- a/src/Console/Commands/Hooks/ESLintPreCommitHook.php +++ b/src/Console/Commands/Hooks/ESLintPreCommitHook.php @@ -33,6 +33,8 @@ public function handle(ChangedFiles $files, Closure $next) return $this->setFileExtensions(config('git-hooks.code_analyzers.eslint.file_extensions')) ->setAnalyzerExecutable(config('git-hooks.code_analyzers.eslint.path'), true) + ->setRunInDocker(config('git-hooks.code_analyzers.eslint.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.eslint.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/src/Console/Commands/Hooks/LarastanPreCommitHook.php b/src/Console/Commands/Hooks/LarastanPreCommitHook.php index 5b7b6f7..32a8c40 100644 --- a/src/Console/Commands/Hooks/LarastanPreCommitHook.php +++ b/src/Console/Commands/Hooks/LarastanPreCommitHook.php @@ -32,6 +32,8 @@ public function handle(ChangedFiles $files, Closure $next) $this->configParam = $this->configParam(); return $this->setAnalyzerExecutable(config('git-hooks.code_analyzers.larastan.path')) + ->setRunInDocker(config('git-hooks.code_analyzers.larastan.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.larastan.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/src/Console/Commands/Hooks/PHPCodeSnifferPreCommitHook.php b/src/Console/Commands/Hooks/PHPCodeSnifferPreCommitHook.php index 2b8e239..07dafce 100644 --- a/src/Console/Commands/Hooks/PHPCodeSnifferPreCommitHook.php +++ b/src/Console/Commands/Hooks/PHPCodeSnifferPreCommitHook.php @@ -34,6 +34,8 @@ public function handle(ChangedFiles $files, Closure $next) return $this->setFileExtensions(config('git-hooks.code_analyzers.php_code_sniffer.file_extensions')) ->setAnalyzerExecutable(config('git-hooks.code_analyzers.php_code_sniffer.phpcs_path')) ->setFixerExecutable(config('git-hooks.code_analyzers.php_code_sniffer.phpcbf_path')) + ->setRunInDocker(config('git-hooks.code_analyzers.php_code_sniffer.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.php_code_sniffer.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/src/Console/Commands/Hooks/PintPreCommitHook.php b/src/Console/Commands/Hooks/PintPreCommitHook.php index 3b7cea8..8bcf4a4 100644 --- a/src/Console/Commands/Hooks/PintPreCommitHook.php +++ b/src/Console/Commands/Hooks/PintPreCommitHook.php @@ -33,6 +33,8 @@ public function handle(ChangedFiles $files, Closure $next) return $this->setFileExtensions(config('git-hooks.code_analyzers.laravel_pint.file_extensions')) ->setAnalyzerExecutable(config('git-hooks.code_analyzers.laravel_pint.path'), true) + ->setRunInDocker(config('git-hooks.code_analyzers.laravel_pint.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.laravel_pint.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/src/Console/Commands/Hooks/PrettierPreCommitHook.php b/src/Console/Commands/Hooks/PrettierPreCommitHook.php index e698251..23c536f 100644 --- a/src/Console/Commands/Hooks/PrettierPreCommitHook.php +++ b/src/Console/Commands/Hooks/PrettierPreCommitHook.php @@ -33,6 +33,8 @@ public function handle(ChangedFiles $files, Closure $next) return $this->setFileExtensions(config('git-hooks.code_analyzers.prettier.file_extensions')) ->setAnalyzerExecutable(config('git-hooks.code_analyzers.prettier.path'), true) + ->setRunInDocker(config('git-hooks.code_analyzers.prettier.run_in_docker')) + ->setDockerContainer(config('git-hooks.code_analyzers.prettier.docker_container')) ->handleCommittedFiles($files, $next); } diff --git a/tests/TestCase.php b/tests/TestCase.php index d8a67c0..f7587bd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -52,6 +52,13 @@ public function defineEnvironment($app) 'pre-push' => [], 'code_analyzers' => [], 'artisan_path' => base_path('artisan'), + 'output_errors' => false, + 'automatically_fix_errors' => false, + 'rerun_analyzer_after_autofix' => false, + 'stop_at_first_analyzer_failure' => true, + 'debug_commands' => false, + 'run_in_docker' => false, + 'docker_command' => '', ]); $this->config = $app['config'];