Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit addec1a

Browse files
authored
Merge pull request #19 from SebSept/pre-commit-hook
Pre commit hook utility
2 parents 2e53529 + eb5ec95 commit addec1a

5 files changed

Lines changed: 255 additions & 18 deletions

File tree

README.md

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ This is just a starting point.
2424
- Code formating : [php-cs-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) configured using prestashop standard, ready to use out of the box.
2525
- Code analysis : [phpstan](https://phpstan.org/) almost ready to use with Prestashop standard, it asks a question then you're ready.
2626
- `fill-indexes` command, to add required index.php files. (see below for details)
27+
- git pre-commit hook installer (details below)
2728

2829
More tools will come
2930
- [prestashop/header-stamp](https://github.com/PrestaShopCorp/header-stamp/) (update license header in files)
30-
- a tool to install a precommit hook to ensure everything is ok before commiting.
3131
- GitHub actions
3232
- ...
3333

@@ -42,23 +42,10 @@ You can even take an additionnal step by [defining an alias](https://duckduckgo.
4242

4343
## Provided commands
4444

45-
* psdt:php-cs-fixer
46-
* psdt:phpstan
47-
* psdt:fill-indexes
48-
49-
### fill-indexes
50-
51-
`composer psdt:fill-indexes`
52-
53-
Add the missing index.php files on each folder.
54-
Existing index.php files are not overriden.
55-
56-
This is a security requirement of Prestashop to avoid the contents to be listed.
57-
58-
More information [on the official documentation](https://devdocs.prestashop.com/1.7/modules/sell/techvalidation-checklist/#a-file-indexphp-exists-in-each-folder).
59-
60-
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.
61-
My replacement is simpler and doesn't require additionnal dependencies.
45+
* [psdt:php-cs-fixer](#fill-indexes)
46+
* [psdt:phpstan](#phpstan)
47+
* [psdt:fill-indexes](#fill-indexes)
48+
* [psdt:install-precommit-hook](#git-pre-commit-hook-installer) (not supported on Windows yet)
6249

6350
### php-cs-fixer
6451

@@ -92,6 +79,35 @@ Autoinstallation provided by this package.
9279

9380
Allows complying with the [Prestashop standards](https://devdocs.prestashop.com/1.7/development/coding-standards/).
9481

82+
83+
### fill-indexes
84+
85+
`composer psdt:fill-indexes`
86+
87+
Add the missing index.php files on each folder.
88+
Existing index.php files are not overriden.
89+
90+
This is a security requirement of Prestashop to avoid the contents to be listed.
91+
92+
More information [on the official documentation](https://devdocs.prestashop.com/1.7/modules/sell/techvalidation-checklist/#a-file-indexphp-exists-in-each-folder).
93+
94+
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.
95+
My replacement is simpler and doesn't require additionnal dependencies.
96+
97+
### Git Pre-commit hook installer
98+
99+
This command need to be run only once.
100+
It does :
101+
- add a composer script `pre-commit`
102+
- add file `precommit.sh`
103+
- make it executable
104+
- symlink it to .git/hooks/pre-commit
105+
106+
So before a commit is can be performed the composer script `pre-commit` must succeed (return 0).
107+
108+
You can tweak the script by just editing the composer script.
109+
You can run the `pre-commit` at any time `composer
110+
95111
## Installation
96112

97113
`composer require --dev sebsept/ps_dev_base`

resources/precommit.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env sh
2+
# this file must be symlinked in .git/hooks with the name "pre-commit"
3+
# it fire composer script "pre-commit" which launch phpstan + phpcsfix
4+
#
5+
# install with `ln -s $(pwd)/precommit.sh .git/hooks/pre-commit`
6+
7+
(cd "./" && echo "start pre-commit hook ..." & exec 'composer' 'run-script' 'pre-commit')

src/Command/BaseCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ final protected function isComposerScriptDefined(): bool
9090
*/
9191
final protected function runComposerScript(OutputInterface $output): void
9292
{
93+
$output->write(sprintf('Now running composer script <info>%s</info> : ', $this->getComposerScriptName()));
94+
9395
$this->getApplication()->find('run-script')->run(
9496
new ArrayInput([
9597
'script' => $this->getComposerScriptName(),
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
/**
3+
* SebSept Ps_dev_base - Tools for quality Prestashop Module development.
4+
*
5+
* Copyright (c) 2021 Sébastien Monterisi
6+
*
7+
* NOTICE OF LICENSE
8+
*
9+
* This source file is subject to the MIT License
10+
* that is bundled with this package in the file LICENSE.txt.
11+
* It is also available through the world-wide-web at this URL:
12+
* https://opensource.org/licenses/MIT
13+
*
14+
* @author Sébastien Monterisi <contact@seb7.fr>
15+
* @copyright since 2021 Sébastien Monterisi
16+
* @license https://opensource.org/licenses/MIT MIT License
17+
*/
18+
19+
declare(strict_types=1);
20+
21+
namespace SebSept\PsDevToolsPlugin\Command\SebSept;
22+
23+
use Exception;
24+
use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsCsFixer;
25+
use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsPhpStan;
26+
use SebSept\PsDevToolsPlugin\Command\ScriptCommand;
27+
use SplFileInfo;
28+
use Symfony\Component\Console\Input\InputInterface;
29+
use Symfony\Component\Console\Input\InputOption;
30+
use Symfony\Component\Console\Output\OutputInterface;
31+
use Symfony\Component\Filesystem\Filesystem;
32+
33+
final class PrecommitHook extends ScriptCommand // implements ConfigurableCommand
34+
{
35+
const SOURCE_PRECOMMIT_FILE = __DIR__ . '/../../../resources/precommit.sh';
36+
const PRECOMMIT_FILE = 'precommit.sh';
37+
const PRECOMMIT_HOOK_FILE = '.git/hooks/pre-commit';
38+
39+
/** @var Filesystem */
40+
private $fs;
41+
42+
final protected function configure(): void
43+
{
44+
$this->setName('install-precommit-hook');
45+
$this->setDescription('Install a git pre-commit hook');
46+
$this->addOption('reconfigure', null, InputOption::VALUE_NONE, 'rerun configuration and file installations.');
47+
$this->setHelp(
48+
$this->getDescription() . <<<'HELP'
49+
50+
* creates file <comment>precommit.sh</comment> that trigger composer script <info>pre-commit</info>
51+
* adds a composer script <info>pre-commit</info>
52+
* symlinks <comment>precommit.sh</comment> to <info>.git/hooks/precommit</info>
53+
* next runs will trigger the composer script <info>pre-commit</info>
54+
55+
The composer scripts by default triggers the other scripts <info>@phpstan</info>, <info>@csfix</info> (dry-run) and <info>composer validate</info>.
56+
This is the default, you can edit the content of the script in <comment>composer.json</comment>
57+
58+
The <comment>--reconfigure</comment> allows to resetup, rerun the 3 first steps and override contents.
59+
60+
This is tested on GNU/Linux, it probably works fine on MacOS. Probably not on Windows (feedback and fix are welcome).
61+
62+
HELP
63+
);
64+
}
65+
66+
protected function execute(InputInterface $input, OutputInterface $output): int
67+
{
68+
try {
69+
$this->fs = new Filesystem();
70+
/** @var bool $reconfigure */
71+
$reconfigure = $input->getOption('reconfigure');
72+
$preCommitHookFileRelativePath = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', self::PRECOMMIT_HOOK_FILE);
73+
74+
if ($reconfigure) {
75+
$composerScriptIsDefined = $precommitFileExists = $precommitFileIsSymlinked = $precommitFileIsExecutable = $readyToRun
76+
= false;
77+
} else {
78+
$composerScriptIsDefined = $this->isComposerScriptDefined();
79+
$precommitFileExists = $this->precommitFileExists();
80+
$precommitFileIsSymlinked = $this->isPrecommitFileSymlinked();
81+
$precommitFileIsExecutable = $this->isPrecommitFileExecutable();
82+
$readyToRun = $composerScriptIsDefined && $precommitFileExists && $precommitFileIsSymlinked && $precommitFileIsExecutable;
83+
}
84+
85+
$composerScriptIsDefined
86+
? $this->getIO()->write(sprintf('<info>Composer script %s is installed.</info>', $this->getComposerScriptName()))
87+
: $this->addComposerScript($this->getComposerScripts());
88+
89+
$precommitFileExists
90+
? $this->getIO()->write(sprintf('<info>Precommit file <comment>%s</comment> is present.</info>', self::PRECOMMIT_FILE))
91+
: $this->copyPrecommitFile();
92+
93+
$precommitFileIsSymlinked
94+
? $this->getIO()->write(sprintf('<info><comment>%s</comment> is symlinked to <comment>%s</comment></info>', self::PRECOMMIT_FILE, $preCommitHookFileRelativePath))
95+
: $this->symLinkPrecommitFile();
96+
97+
$precommitFileIsExecutable
98+
? $this->getIO()->write(sprintf('<info><comment>%s</comment> is executable.</info>', $preCommitHookFileRelativePath))
99+
: $this->makePrecommitFileExecutable();
100+
101+
$reconfigure && $this->getIO()->write($this->getAdditionnalHelp());
102+
103+
$readyToRun
104+
? $this->runComposerScript($output)
105+
: $this->getIO()->write(sprintf('run <info>%s</info> command again to run composer script <info>%s</info>', $this->getName(), $this->getComposerScriptName()));
106+
107+
return 0;
108+
} catch (Exception $exception) {
109+
$this->getIO()->error(sprintf('%s failed : %s', $this->getComposerScriptName(), $exception->getMessage()));
110+
// throw $exception; // for debug purpose.
111+
return 1;
112+
}
113+
}
114+
115+
/**
116+
* Name of composer script.
117+
* In case it change, it also needs to be changed in the information text displayed.
118+
*
119+
* @see execute()
120+
*/
121+
public function getComposerScriptName(): string
122+
{
123+
return 'pre-commit';
124+
}
125+
126+
/**
127+
* Composer scripts to launch depending if tools are configured.
128+
*
129+
* @return array<int, string>
130+
*/
131+
private function getComposerScripts(): array
132+
{
133+
$scripts = ['composer validate'];
134+
!(new PrestashopDevToolsCsFixer())->isToolConfigured()
135+
?: array_push($scripts, 'vendor/bin/php-cs-fixer fix --dry-run --ansi');
136+
!(new PrestashopDevToolsPhpStan())->isToolConfigured()
137+
?: array_push($scripts, '@phpstan');
138+
139+
return $scripts;
140+
}
141+
142+
private function precommitFileExists(): bool
143+
{
144+
return $this->fs->exists(self::PRECOMMIT_FILE);
145+
}
146+
147+
private function copyPrecommitFile(): void
148+
{
149+
$this->getIO()->write('Copying pre-commit script file ...', false);
150+
$this->fs->copy(self::SOURCE_PRECOMMIT_FILE, self::PRECOMMIT_FILE, true);
151+
$this->getIO()->write('<info>OK</info>');
152+
}
153+
154+
private function isPrecommitFileSymlinked(): bool
155+
{
156+
$gitPrecommitFile = new SplFileInfo(getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_HOOK_FILE);
157+
$precommitFile = new SplFileInfo(getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_FILE);
158+
159+
return $gitPrecommitFile->isLink()
160+
&& $precommitFile->getPathname() === $gitPrecommitFile->getLinkTarget();
161+
}
162+
163+
private function symLinkPrecommitFile(): void
164+
{
165+
$gitLink = getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_HOOK_FILE;
166+
$precommitScript = getcwd() . DIRECTORY_SEPARATOR . self::PRECOMMIT_FILE;
167+
168+
// remove existing git file
169+
if (file_exists($gitLink)) {
170+
$this->getIO()->write('Removing git hook...', false);
171+
if (!unlink($gitLink)) {
172+
throw new Exception('Failed to remove existing pre-commit file ' . $gitLink);
173+
}
174+
$this->getIO()->write(' <info>OK</info>');
175+
}
176+
177+
// create symlink
178+
$this->getIO()->write('Symlink to precommit hook...', false);
179+
if (!symlink($precommitScript, $gitLink)) {
180+
throw new Exception('Failed to symlink.');
181+
}
182+
$this->getIO()->write(' <info>OK</info>');
183+
}
184+
185+
private function isPrecommitFileExecutable(): bool
186+
{
187+
return (new SplFileInfo(self::PRECOMMIT_FILE))->isExecutable();
188+
}
189+
190+
private function makePrecommitFileExecutable(): void
191+
{
192+
$this->getIO()->write('Make pre-commit script file executable ...', false);
193+
$this->fs->chmod(self::PRECOMMIT_FILE, 0755);
194+
$this->getIO()->write('<info>OK</info>');
195+
}
196+
197+
private function getAdditionnalHelp(): string
198+
{
199+
return <<<'INFOS'
200+
201+
Before the next commit the git precommit hook will be triggered.
202+
If the pre-commit script return 0 (success), commit will be performed, otherwise aborted.
203+
In case, you can't read the precommit script output and find what's wrong
204+
just run <info>composer psdt:pre-commit</info> .
205+
206+
You can also run this command at any time, before processing the commit, stashing changes for example.
207+
You can edit the script content by editing the script entry <info>pre-commit</info> in <comment>composer.json</comment>.
208+
INFOS;
209+
}
210+
}

src/Composer/PsDevToolsCommandProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use SebSept\PsDevToolsPlugin\Command\PrestashopDevTools\PrestashopDevToolsPhpStan;
2626
use SebSept\PsDevToolsPlugin\Command\SebSept\HelloCommand;
2727
use SebSept\PsDevToolsPlugin\Command\SebSept\IndexPhpFiller;
28+
use SebSept\PsDevToolsPlugin\Command\SebSept\PrecommitHook;
2829

2930
final class PsDevToolsCommandProvider implements CommandProvider
3031
{
@@ -35,6 +36,7 @@ public function getCommands(): array
3536
new PrestashopDevToolsPhpStan(),
3637
new PrestashopDevToolsCsFixer(),
3738
new IndexPhpFiller(),
39+
new PrecommitHook(),
3840
];
3941
}
4042
}

0 commit comments

Comments
 (0)