diff --git a/README.md b/README.md
index dd60217..ec1380e 100644
--- a/README.md
+++ b/README.md
@@ -24,10 +24,10 @@ This is just a starting point.
- Code formating : [php-cs-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) configured using prestashop standard, ready to use out of the box.
- Code analysis : [phpstan](https://phpstan.org/) almost ready to use with Prestashop standard, it asks a question then you're ready.
- `fill-indexes` command, to add required index.php files. (see below for details)
+- git pre-commit hook installer (details below)
More tools will come
- [prestashop/header-stamp](https://github.com/PrestaShopCorp/header-stamp/) (update license header in files)
-- a tool to install a precommit hook to ensure everything is ok before commiting.
- GitHub actions
- ...
@@ -42,23 +42,10 @@ You can even take an additionnal step by [defining an alias](https://duckduckgo.
## Provided commands
-* psdt:php-cs-fixer
-* psdt:phpstan
-* psdt:fill-indexes
-
-### fill-indexes
-
-`composer psdt:fill-indexes`
-
-Add the missing index.php files on each folder.
-Existing index.php files are not overriden.
-
-This is a security requirement of Prestashop to avoid the contents to be listed.
-
-More information [on the official documentation](https://devdocs.prestashop.com/1.7/modules/sell/techvalidation-checklist/#a-file-indexphp-exists-in-each-folder).
-
-I can't include [prestashop/autoindex](https://github.com/PrestaShopCorp/autoindex) because [it targets php 5.6](https://github.com/PrestaShopCorp/autoindex/blob/92e10242f94a99163dece280f6bd7b7c2b79c158/composer.json#L23) and has other issues.
-My replacement is simpler and doesn't require additionnal dependencies.
+* [psdt:php-cs-fixer](#fill-indexes)
+* [psdt:phpstan](#phpstan)
+* [psdt:fill-indexes](#fill-indexes)
+* [psdt:install-precommit-hook](#git-pre-commit-hook-installer) (not supported on Windows yet)
### php-cs-fixer
@@ -92,6 +79,35 @@ Autoinstallation provided by this package.
Allows complying with the [Prestashop standards](https://devdocs.prestashop.com/1.7/development/coding-standards/).
+
+### fill-indexes
+
+`composer psdt:fill-indexes`
+
+Add the missing index.php files on each folder.
+Existing index.php files are not overriden.
+
+This is a security requirement of Prestashop to avoid the contents to be listed.
+
+More information [on the official documentation](https://devdocs.prestashop.com/1.7/modules/sell/techvalidation-checklist/#a-file-indexphp-exists-in-each-folder).
+
+I can't include [prestashop/autoindex](https://github.com/PrestaShopCorp/autoindex) because [it targets php 5.6](https://github.com/PrestaShopCorp/autoindex/blob/92e10242f94a99163dece280f6bd7b7c2b79c158/composer.json#L23) and has other issues.
+My replacement is simpler and doesn't require additionnal dependencies.
+
+### Git Pre-commit hook installer
+
+This command need to be run only once.
+It does :
+- add a composer script `pre-commit`
+- add file `precommit.sh`
+- make it executable
+- symlink it to .git/hooks/pre-commit
+
+So before a commit is can be performed the composer script `pre-commit` must succeed (return 0).
+
+You can tweak the script by just editing the composer script.
+You can run the `pre-commit` at any time `composer
+
## Installation
`composer require --dev sebsept/ps_dev_base`
diff --git a/resources/precommit.sh b/resources/precommit.sh
new file mode 100644
index 0000000..011ec90
--- /dev/null
+++ b/resources/precommit.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+# this file must be symlinked in .git/hooks with the name "pre-commit"
+# it fire composer script "pre-commit" which launch phpstan + phpcsfix
+#
+# install with `ln -s $(pwd)/precommit.sh .git/hooks/pre-commit`
+
+(cd "./" && echo "start pre-commit hook ..." & exec 'composer' 'run-script' 'pre-commit')
\ No newline at end of file
diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php
index dd415f8..c66cb1e 100644
--- a/src/Command/BaseCommand.php
+++ b/src/Command/BaseCommand.php
@@ -90,6 +90,8 @@ final protected function isComposerScriptDefined(): bool
*/
final protected function runComposerScript(OutputInterface $output): void
{
+ $output->write(sprintf('Now running composer script %s : ', $this->getComposerScriptName()));
+
$this->getApplication()->find('run-script')->run(
new ArrayInput([
'script' => $this->getComposerScriptName(),
diff --git a/src/Command/SebSept/PrecommitHook.php b/src/Command/SebSept/PrecommitHook.php
new file mode 100644
index 0000000..03d3978
--- /dev/null
+++ b/src/Command/SebSept/PrecommitHook.php
@@ -0,0 +1,210 @@
+
+ * @copyright since 2021 Sébastien Monterisi
+ * @license https://opensource.org/licenses/MIT MIT License
+ */
+
+declare(strict_types=1);
+
+namespace SebSept\PsDevToolsPlugin\Command\SebSept;
+
+use Exception;
+use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsCsFixer;
+use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsPhpStan;
+use SebSept\PsDevToolsPlugin\Command\ScriptCommand;
+use SplFileInfo;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Filesystem\Filesystem;
+
+final class PrecommitHook extends ScriptCommand // implements ConfigurableCommand
+{
+ const SOURCE_PRECOMMIT_FILE = __DIR__ . '/../../../resources/precommit.sh';
+ const PRECOMMIT_FILE = 'precommit.sh';
+ const PRECOMMIT_HOOK_FILE = '.git/hooks/pre-commit';
+
+ /** @var Filesystem */
+ private $fs;
+
+ final protected function configure(): void
+ {
+ $this->setName('install-precommit-hook');
+ $this->setDescription('Install a git pre-commit hook');
+ $this->addOption('reconfigure', null, InputOption::VALUE_NONE, 'rerun configuration and file installations.');
+ $this->setHelp(
+ $this->getDescription() . <<<'HELP'
+
+ * creates file precommit.sh that trigger composer script pre-commit
+ * adds a composer script pre-commit
+ * symlinks precommit.sh to .git/hooks/precommit
+ * next runs will trigger the composer script pre-commit
+
+The composer scripts by default triggers the other scripts @phpstan, @csfix (dry-run) and composer validate.
+This is the default, you can edit the content of the script in composer.json
+
+The --reconfigure allows to resetup, rerun the 3 first steps and override contents.
+
+This is tested on GNU/Linux, it probably works fine on MacOS. Probably not on Windows (feedback and fix are welcome).
+
+HELP
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ $this->fs = new Filesystem();
+ /** @var bool $reconfigure */
+ $reconfigure = $input->getOption('reconfigure');
+ $preCommitHookFileRelativePath = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', self::PRECOMMIT_HOOK_FILE);
+
+ if ($reconfigure) {
+ $composerScriptIsDefined = $precommitFileExists = $precommitFileIsSymlinked = $precommitFileIsExecutable = $readyToRun
+ = false;
+ } else {
+ $composerScriptIsDefined = $this->isComposerScriptDefined();
+ $precommitFileExists = $this->precommitFileExists();
+ $precommitFileIsSymlinked = $this->isPrecommitFileSymlinked();
+ $precommitFileIsExecutable = $this->isPrecommitFileExecutable();
+ $readyToRun = $composerScriptIsDefined && $precommitFileExists && $precommitFileIsSymlinked && $precommitFileIsExecutable;
+ }
+
+ $composerScriptIsDefined
+ ? $this->getIO()->write(sprintf('Composer script %s is installed.', $this->getComposerScriptName()))
+ : $this->addComposerScript($this->getComposerScripts());
+
+ $precommitFileExists
+ ? $this->getIO()->write(sprintf('Precommit file %s is present.', self::PRECOMMIT_FILE))
+ : $this->copyPrecommitFile();
+
+ $precommitFileIsSymlinked
+ ? $this->getIO()->write(sprintf('%s is symlinked to %s', self::PRECOMMIT_FILE, $preCommitHookFileRelativePath))
+ : $this->symLinkPrecommitFile();
+
+ $precommitFileIsExecutable
+ ? $this->getIO()->write(sprintf('%s is executable.', $preCommitHookFileRelativePath))
+ : $this->makePrecommitFileExecutable();
+
+ $reconfigure && $this->getIO()->write($this->getAdditionnalHelp());
+
+ $readyToRun
+ ? $this->runComposerScript($output)
+ : $this->getIO()->write(sprintf('run %s command again to run composer script %s', $this->getName(), $this->getComposerScriptName()));
+
+ return 0;
+ } catch (Exception $exception) {
+ $this->getIO()->error(sprintf('%s failed : %s', $this->getComposerScriptName(), $exception->getMessage()));
+ // throw $exception; // for debug purpose.
+ return 1;
+ }
+ }
+
+ /**
+ * Name of composer script.
+ * In case it change, it also needs to be changed in the information text displayed.
+ *
+ * @see execute()
+ */
+ public function getComposerScriptName(): string
+ {
+ return 'pre-commit';
+ }
+
+ /**
+ * Composer scripts to launch depending if tools are configured.
+ *
+ * @return array
+ */
+ private function getComposerScripts(): array
+ {
+ $scripts = ['composer validate'];
+ !(new PrestashopDevToolsCsFixer())->isToolConfigured()
+ ?: array_push($scripts, 'vendor/bin/php-cs-fixer fix --dry-run --ansi');
+ !(new PrestashopDevToolsPhpStan())->isToolConfigured()
+ ?: array_push($scripts, '@phpstan');
+
+ return $scripts;
+ }
+
+ private function precommitFileExists(): bool
+ {
+ return $this->fs->exists(self::PRECOMMIT_FILE);
+ }
+
+ private function copyPrecommitFile(): void
+ {
+ $this->getIO()->write('Copying pre-commit script file ...', false);
+ $this->fs->copy(self::SOURCE_PRECOMMIT_FILE, self::PRECOMMIT_FILE, true);
+ $this->getIO()->write('OK');
+ }
+
+ private function isPrecommitFileSymlinked(): bool
+ {
+ $gitPrecommitFile = new SplFileInfo(getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_HOOK_FILE);
+ $precommitFile = new SplFileInfo(getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_FILE);
+
+ return $gitPrecommitFile->isLink()
+ && $precommitFile->getPathname() === $gitPrecommitFile->getLinkTarget();
+ }
+
+ private function symLinkPrecommitFile(): void
+ {
+ $gitLink = getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_HOOK_FILE;
+ $precommitScript = getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_FILE;
+
+ // remove existing git file
+ if (file_exists($gitLink)) {
+ $this->getIO()->write('Removing git hook...', false);
+ if (!unlink($gitLink)) {
+ throw new Exception('Failed to remove existing pre-commit file ' . $gitLink);
+ }
+ $this->getIO()->write(' OK');
+ }
+
+ // create symlink
+ $this->getIO()->write('Symlink to precommit hook...', false);
+ if (!symlink($precommitScript, $gitLink)) {
+ throw new Exception('Failed to symlink.');
+ }
+ $this->getIO()->write(' OK');
+ }
+
+ private function isPrecommitFileExecutable(): bool
+ {
+ return (new SplFileInfo(self::PRECOMMIT_FILE))->isExecutable();
+ }
+
+ private function makePrecommitFileExecutable(): void
+ {
+ $this->getIO()->write('Make pre-commit script file executable ...', false);
+ $this->fs->chmod(self::PRECOMMIT_FILE, 0755);
+ $this->getIO()->write('OK');
+ }
+
+ private function getAdditionnalHelp(): string
+ {
+ return <<<'INFOS'
+
+Before the next commit the git precommit hook will be triggered.
+If the pre-commit script return 0 (success), commit will be performed, otherwise aborted.
+In case, you can't read the precommit script output and find what's wrong
+just run composer psdt:pre-commit .
+
+You can also run this command at any time, before processing the commit, stashing changes for example.
+You can edit the script content by editing the script entry pre-commit in composer.json.
+INFOS;
+ }
+}
diff --git a/src/Composer/PsDevToolsCommandProvider.php b/src/Composer/PsDevToolsCommandProvider.php
index 820eb86..02e7da0 100644
--- a/src/Composer/PsDevToolsCommandProvider.php
+++ b/src/Composer/PsDevToolsCommandProvider.php
@@ -25,6 +25,7 @@
use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsPhpStan;
use SebSept\PsDevToolsPlugin\Command\SebSept\HelloCommand;
use SebSept\PsDevToolsPlugin\Command\SebSept\IndexPhpFiller;
+use SebSept\PsDevToolsPlugin\Command\SebSept\PrecommitHook;
final class PsDevToolsCommandProvider implements CommandProvider
{
@@ -35,6 +36,7 @@ public function getCommands(): array
new PrestashopDevToolsPhpStan(),
new PrestashopDevToolsCsFixer(),
new IndexPhpFiller(),
+ new PrecommitHook(),
];
}
}