Skip to content

Commit addd702

Browse files
Adopt version 5 changes
Execute CaptainHook in a sub process to avoid autoloader issues. - Allow PHAR usage - Change extra config structure - Remove captainhook package dependency
1 parent 61b6c50 commit addd702

File tree

2 files changed

+197
-21
lines changed

2 files changed

+197
-21
lines changed

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
],
1616
"require": {
1717
"php": "^7.1",
18-
"composer-plugin-api": "^1.1",
19-
"captainhook/captainhook": "^4.0"
18+
"composer-plugin-api": "^1.1"
2019
},
2120
"require-dev": {
2221
"composer/composer": "*"

src/ComposerPlugin.php

Lines changed: 196 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111

1212
namespace CaptainHook\Plugin\Composer;
1313

14-
use CaptainHook\App\Composer\Cmd;
1514
use Composer\Composer;
1615
use Composer\EventDispatcher\EventSubscriberInterface;
1716
use Composer\IO\IOInterface;
1817
use Composer\Plugin\PluginInterface;
1918
use Composer\Script\Event;
2019
use Composer\Script\ScriptEvents;
20+
use RuntimeException;
2121

2222
/**
2323
* Class ComposerPlugin
@@ -28,24 +28,63 @@
2828
*/
2929
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
3030
{
31+
private const COMMAND_CONFIGURE = 'configure';
32+
private const COMMAND_INSTALL = 'install';
33+
34+
/**
35+
* Composer instance
36+
*
37+
* @var \Composer\Composer
38+
*/
39+
private $composer;
40+
41+
/**
42+
* Composer IO instance
43+
*
44+
* @var \Composer\IO\IOInterface
45+
*/
46+
private $io;
47+
48+
/**
49+
* Path to the captainhook executable
50+
*
51+
* @var string
52+
*/
53+
private $executable;
54+
55+
/**
56+
* Path to the captainhook configuration file
57+
*
58+
* @var string
59+
*/
60+
private $configuration;
61+
62+
/**
63+
* Path to the .git directory
64+
*
65+
* @var string
66+
*/
67+
private $gitDirectory;
68+
3169
/**
3270
* Activate the plugin
3371
*
3472
* @param \Composer\Composer $composer
3573
* @param \Composer\IO\IOInterface $io
3674
* @return void
3775
*/
38-
public function activate(Composer $composer, IOInterface $io) : void
76+
public function activate(Composer $composer, IOInterface $io): void
3977
{
40-
// nothing to do here
78+
$this->composer = $composer;
79+
$this->io = $io;
4180
}
4281

4382
/**
4483
* Make sure the installer is executed after the autoloader is created
4584
*
4685
* @return array
4786
*/
48-
public static function getSubscribedEvents() : array
87+
public static function getSubscribedEvents(): array
4988
{
5089
return [
5190
ScriptEvents::POST_AUTOLOAD_DUMP => 'installHooks'
@@ -59,21 +98,33 @@ public static function getSubscribedEvents() : array
5998
* @return void
6099
* @throws \Exception
61100
*/
62-
public function installHooks(Event $event) : void
101+
public function installHooks(Event $event): void
63102
{
64-
$event->getIO()->write('CaptainHook Composer Plugin');
65-
if (!$this->isCaptainHookInstalled()) {
66-
// reload the autoloader to make sure CaptainHook is available
67-
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
68-
require $vendorDir . '/autoload.php';
103+
$this->io->write('CaptainHook Composer Plugin');
104+
105+
if ($this->isPluginDisabled()) {
106+
$this->io->write('<info>plugin is disabled</info>');
107+
return;
69108
}
70109

71-
if (!$this->isCaptainHookInstalled()) {
72-
// if CaptainHook is still not available end the plugin execution
73-
// normally this only happens if CaptainHook gets uninstalled
74-
$event->getIO()->write(
75-
' <info>CaptainHook not properly installed try to run composer update</info>' . PHP_EOL .
110+
$this->detectConfiguration();
111+
$this->detectGitDir();
112+
$this->detectCaptainExecutable();
113+
114+
if (!file_exists($this->executable)) {
115+
$this->io->write(
116+
'<info>CaptainHook executable not found</info>' . PHP_EOL .
76117
PHP_EOL .
118+
'Make sure you have installed the captainhook/captainhook package.' . PHP_EOL .
119+
'If you are using the PHAR you have to configure the path to your CaptainHook executable' . PHP_EOL .
120+
'using Composers \'extra\' config. e.g.' . PHP_EOL .
121+
PHP_EOL . '<comment>' .
122+
' "extra": {' . PHP_EOL .
123+
' "captainhook": {' . PHP_EOL .
124+
' "exec": "tools/captainhook' . PHP_EOL .
125+
' }' . PHP_EOL .
126+
' }' . PHP_EOL .
127+
'</comment>' . PHP_EOL .
77128
'If you are uninstalling CaptainHook, we are sad seeing you go, ' .
78129
'but we would appreciate your feedback on your experience.' . PHP_EOL .
79130
'Just go to https://github.com/CaptainHookPhp/captainhook/issues to leave your feedback' . PHP_EOL .
@@ -82,16 +133,142 @@ public function installHooks(Event $event) : void
82133
);
83134
return;
84135
}
85-
Cmd::setup($event);
136+
137+
$this->configure();
138+
$this->install();
139+
}
140+
141+
/**
142+
* Create captainhook.json file if it does not exist
143+
*/
144+
private function configure(): void
145+
{
146+
if (file_exists($this->configuration)) {
147+
$this->io->write(('<info>Using CaptainHook config: ' . $this->configuration . '</info>'));
148+
return;
149+
}
150+
151+
$this->runCaptainCommand(self::COMMAND_CONFIGURE);
152+
}
153+
154+
/**
155+
* Install hooks to your .git/hooks directory
156+
*/
157+
private function install(): void
158+
{
159+
$this->runCaptainCommand(self::COMMAND_INSTALL);
160+
}
161+
162+
/**
163+
* Executes CaptainHook in a sub process
164+
*
165+
* @param string $command
166+
*/
167+
private function runCaptainCommand(string $command): void
168+
{
169+
// Respect composer CLI settings
170+
$ansi = $this->io->isDecorated() ? ' --ansi' : ' --no-ansi';
171+
$interaction = $this->io->isInteractive() ? '' : ' --no-interaction';
172+
173+
// captainhook config and repository settings
174+
$configuration = ' -c ' . $this->configuration;
175+
$repository = ' -g ' . $this->gitDirectory;
176+
$skip = $command === self::COMMAND_INSTALL ? ' -s' : '';
177+
178+
// sub process settings
179+
$cmd = $this->executable . ' ' . $command . $ansi . $interaction . $skip . $configuration . $repository;
180+
$pipes = [];
181+
$spec = [
182+
0 => ['file', 'php://stdin', 'r'],
183+
1 => ['file', 'php://stdout', 'w'],
184+
2 => ['file', 'php://stderr', 'w'],
185+
];
186+
187+
$process = @proc_open($cmd, $spec, $pipes);
188+
189+
if ($this->io->isVerbose()) {
190+
$this->io->write('Running process : ' . $cmd);
191+
}
192+
if (!is_resource($process)) {
193+
throw new RuntimeException($this->pluginErrorMessage('no-process'));
194+
}
195+
196+
// Loop on process until it exits normally.
197+
do {
198+
$status = proc_get_status($process);
199+
} while ($status && $status['running']);
200+
$exitCode = $status['exitcode'] ?? -1;
201+
proc_close($process);
202+
if ($exitCode !== 0) {
203+
throw new RuntimeException($this->pluginErrorMessage('invalid-exit-code'));
204+
}
205+
}
206+
207+
/**
208+
* Return path to the CaptainHook configuration file
209+
*
210+
* @return void
211+
*/
212+
private function detectConfiguration(): void
213+
{
214+
$extra = $this->composer->getPackage()->getExtra();
215+
$this->configuration = getcwd() . '/' . ($extra['captainhook']['config'] ?? 'captainhook.json');
216+
}
217+
218+
/**
219+
* Search for the git repository to store the hooks in
220+
221+
* @return void
222+
* @throws \RuntimeException
223+
*/
224+
private function detectGitDir(): void
225+
{
226+
$path = getcwd();
227+
228+
while (file_exists($path)) {
229+
$possibleGitDir = $path . '/.git';
230+
if (is_dir($possibleGitDir)) {
231+
$this->gitDirectory = $possibleGitDir;
232+
return;
233+
}
234+
$path = \dirname($path);
235+
}
236+
throw new RuntimeException($this->pluginErrorMessage('git directory not found'));
237+
}
238+
239+
/**
240+
* Creates a nice formatted error message
241+
*
242+
* @param string $reason
243+
* @return string
244+
*/
245+
private function pluginErrorMessage(string $reason): string
246+
{
247+
return 'Shiver me timbers! CaptainHook could not install yer git hooks! (' . $reason . ')';
248+
}
249+
250+
/**
251+
*
252+
*/
253+
private function detectCaptainExecutable(): void
254+
{
255+
$extra = $this->composer->getPackage()->getExtra();
256+
if (isset($extra['captainhook']['exec'])) {
257+
$this->executable = $extra['captainhook']['exec'];
258+
return;
259+
}
260+
261+
$this->executable = (string) $this->composer->getConfig()->get('bin-dir') . '/captainhook';
86262
}
87263

88264
/**
89-
* Checks if CaptainHook is installed properly
265+
* Check if the plugin is disabled
90266
*
91267
* @return bool
92268
*/
93-
private function isCaptainHookInstalled() : bool
269+
private function isPluginDisabled(): bool
94270
{
95-
return class_exists('\\CaptainHook\\App\\CH');
271+
$extra = $this->composer->getPackage()->getExtra();
272+
return (bool) ($extra['captainhook']['disable-plugin'] ?? false);
96273
}
97274
}

0 commit comments

Comments
 (0)