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

Commit

Permalink
Merge pull request #19 from SebSept/pre-commit-hook
Browse files Browse the repository at this point in the history
Pre commit hook utility
  • Loading branch information
SebSept authored May 12, 2021
2 parents 2e53529 + eb5ec95 commit addec1a
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 18 deletions.
52 changes: 34 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- ...

Expand All @@ -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

Expand Down Expand Up @@ -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`
Expand Down
7 changes: 7 additions & 0 deletions resources/precommit.sh
Original file line number Diff line number Diff line change
@@ -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')
2 changes: 2 additions & 0 deletions src/Command/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ final protected function isComposerScriptDefined(): bool
*/
final protected function runComposerScript(OutputInterface $output): void
{
$output->write(sprintf('Now running composer script <info>%s</info> : ', $this->getComposerScriptName()));

$this->getApplication()->find('run-script')->run(
new ArrayInput([
'script' => $this->getComposerScriptName(),
Expand Down
210 changes: 210 additions & 0 deletions src/Command/SebSept/PrecommitHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php
/**
* SebSept Ps_dev_base - Tools for quality Prestashop Module development.
*
* Copyright (c) 2021 Sébastien Monterisi
*
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/MIT
*
* @author Sébastien Monterisi <[email protected]>
* @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 <comment>precommit.sh</comment> that trigger composer script <info>pre-commit</info>
* adds a composer script <info>pre-commit</info>
* symlinks <comment>precommit.sh</comment> to <info>.git/hooks/precommit</info>
* next runs will trigger the composer script <info>pre-commit</info>
The composer scripts by default triggers the other scripts <info>@phpstan</info>, <info>@csfix</info> (dry-run) and <info>composer validate</info>.
This is the default, you can edit the content of the script in <comment>composer.json</comment>
The <comment>--reconfigure</comment> 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('<info>Composer script %s is installed.</info>', $this->getComposerScriptName()))
: $this->addComposerScript($this->getComposerScripts());

$precommitFileExists
? $this->getIO()->write(sprintf('<info>Precommit file <comment>%s</comment> is present.</info>', self::PRECOMMIT_FILE))
: $this->copyPrecommitFile();

$precommitFileIsSymlinked
? $this->getIO()->write(sprintf('<info><comment>%s</comment> is symlinked to <comment>%s</comment></info>', self::PRECOMMIT_FILE, $preCommitHookFileRelativePath))
: $this->symLinkPrecommitFile();

$precommitFileIsExecutable
? $this->getIO()->write(sprintf('<info><comment>%s</comment> is executable.</info>', $preCommitHookFileRelativePath))
: $this->makePrecommitFileExecutable();

$reconfigure && $this->getIO()->write($this->getAdditionnalHelp());

$readyToRun
? $this->runComposerScript($output)
: $this->getIO()->write(sprintf('run <info>%s</info> command again to run composer script <info>%s</info>', $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<int, string>
*/
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('<info>OK</info>');
}

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(' <info>OK</info>');
}

// create symlink
$this->getIO()->write('Symlink to precommit hook...', false);
if (!symlink($precommitScript, $gitLink)) {
throw new Exception('Failed to symlink.');
}
$this->getIO()->write(' <info>OK</info>');
}

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('<info>OK</info>');
}

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 <info>composer psdt:pre-commit</info> .
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 <info>pre-commit</info> in <comment>composer.json</comment>.
INFOS;
}
}
2 changes: 2 additions & 0 deletions src/Composer/PsDevToolsCommandProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -35,6 +36,7 @@ public function getCommands(): array
new PrestashopDevToolsPhpStan(),
new PrestashopDevToolsCsFixer(),
new IndexPhpFiller(),
new PrecommitHook(),
];
}
}

0 comments on commit addec1a

Please sign in to comment.