1111
1212namespace CaptainHook \Plugin \Composer ;
1313
14- use CaptainHook \App \Composer \Cmd ;
1514use Composer \Composer ;
1615use Composer \EventDispatcher \EventSubscriberInterface ;
1716use Composer \IO \IOInterface ;
1817use Composer \Plugin \PluginInterface ;
1918use Composer \Script \Event ;
2019use Composer \Script \ScriptEvents ;
20+ use RuntimeException ;
2121
2222/**
2323 * Class ComposerPlugin
2828 */
2929class 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 /**
@@ -75,10 +114,11 @@ public function uninstall(Composer $composer, IOInterface $io)
75114 *
76115 * @return array
77116 */
78- public static function getSubscribedEvents () : array
117+ public static function getSubscribedEvents (): array
79118 {
80119 return [
81- ScriptEvents::POST_AUTOLOAD_DUMP => 'installHooks '
120+ ScriptEvents::POST_INSTALL_CMD => 'installHooks ' ,
121+ ScriptEvents::POST_UPDATE_CMD => 'installHooks '
82122 ];
83123 }
84124
@@ -89,39 +129,184 @@ public static function getSubscribedEvents() : array
89129 * @return void
90130 * @throws \Exception
91131 */
92- public function installHooks (Event $ event ) : void
132+ public function installHooks (Event $ event ): void
93133 {
94- $ event -> getIO () ->write ('CaptainHook Composer Plugin ' );
95- if (! $ this -> isCaptainHookInstalled ()) {
96- // reload the autoloader to make sure CaptainHook is available
97- $ vendorDir = $ event -> getComposer ()-> getConfig ()-> get ( ' vendor-dir ' );
98- require $ vendorDir . ' /autoload.php ' ;
134+ $ this -> io ->write ('<info> CaptainHook Composer Plugin</info> ' );
135+
136+ if ( $ this -> isPluginDisabled ()) {
137+ $ this -> io -> write ( ' <comment>plugin is disabled</comment> ' );
138+ return ;
99139 }
100140
101- if (!$ this ->isCaptainHookInstalled ()) {
102- // if CaptainHook is still not available end the plugin execution
103- // normally this only happens if CaptainHook gets uninstalled
104- $ event ->getIO ()->write (
105- ' <info>CaptainHook not properly installed try to run composer update</info> ' . PHP_EOL .
141+ $ this ->detectConfiguration ();
142+ $ this ->detectGitDir ();
143+ $ this ->detectCaptainExecutable ();
144+
145+ if (!file_exists ($ this ->executable )) {
146+ $ this ->io ->write (
147+ '<comment>CaptainHook executable not found</comment> ' . PHP_EOL .
106148 PHP_EOL .
149+ 'Make sure you have installed the captainhook/captainhook package. ' . PHP_EOL .
150+ 'If you are using the PHAR you have to configure the path to your CaptainHook executable ' . PHP_EOL .
151+ 'using Composers \'extra \' config. e.g. ' . PHP_EOL .
152+ PHP_EOL . '<comment> ' .
153+ ' "extra": { ' . PHP_EOL .
154+ ' "captainhook": { ' . PHP_EOL .
155+ ' "exec": "tools/captainhook.phar ' . PHP_EOL .
156+ ' } ' . PHP_EOL .
157+ ' } ' . PHP_EOL .
158+ '</comment> ' . PHP_EOL .
107159 'If you are uninstalling CaptainHook, we are sad seeing you go, ' .
108160 'but we would appreciate your feedback on your experience. ' . PHP_EOL .
109161 'Just go to https://github.com/CaptainHookPhp/captainhook/issues to leave your feedback ' . PHP_EOL .
110- PHP_EOL .
111- ' <comment>WARNING: Don \' t forget to deactivate the hooks in your .git/hooks directory.</comment> '
162+ ' <comment>WARNING: Don \' t forget to deactivate the hooks in your .git/hooks directory.</comment> ' .
163+ PHP_EOL
112164 );
113165 return ;
114166 }
115- Cmd::setup ($ event );
167+
168+ $ this ->configure ();
169+ $ this ->install ();
170+ }
171+
172+ /**
173+ * Create captainhook.json file if it does not exist
174+ */
175+ private function configure (): void
176+ {
177+ if (file_exists ($ this ->configuration )) {
178+ $ this ->io ->write ((' <comment>Using CaptainHook config: ' . $ this ->configuration . '</comment> ' ));
179+ return ;
180+ }
181+
182+ $ this ->runCaptainCommand (self ::COMMAND_CONFIGURE );
183+ }
184+
185+ /**
186+ * Install hooks to your .git/hooks directory
187+ */
188+ private function install (): void
189+ {
190+ $ this ->runCaptainCommand (self ::COMMAND_INSTALL );
191+ }
192+
193+ /**
194+ * Executes CaptainHook in a sub process
195+ *
196+ * @param string $command
197+ */
198+ private function runCaptainCommand (string $ command ): void
199+ {
200+ // Respect composer CLI settings
201+ $ ansi = $ this ->io ->isDecorated () ? ' --ansi ' : ' --no-ansi ' ;
202+ $ interaction = $ this ->io ->isInteractive () ? '' : ' --no-interaction ' ;
203+
204+ // captainhook config and repository settings
205+ $ configuration = ' -c ' . escapeshellarg ($ this ->configuration );
206+ $ repository = $ command === self ::COMMAND_INSTALL ? ' -g ' . escapeshellarg ($ this ->gitDirectory ) : '' ;
207+ $ skip = $ command === self ::COMMAND_INSTALL ? ' -s ' : '' ;
208+ $ executable = str_replace (' ' , '\\ ' , $ this ->executable );
209+
210+ // sub process settings
211+ $ cmd = $ executable . ' ' . $ command . $ ansi . $ interaction . $ skip . $ configuration . $ repository ;
212+ $ pipes = [];
213+ $ spec = [
214+ 0 => ['file ' , 'php://stdin ' , 'r ' ],
215+ 1 => ['file ' , 'php://stdout ' , 'w ' ],
216+ 2 => ['file ' , 'php://stderr ' , 'w ' ],
217+ ];
218+
219+ $ process = @proc_open ($ cmd , $ spec , $ pipes );
220+
221+ if ($ this ->io ->isVerbose ()) {
222+ $ this ->io ->write ('Running process : ' . $ cmd );
223+ }
224+ if (!is_resource ($ process )) {
225+ throw new RuntimeException ($ this ->pluginErrorMessage ('no-process ' ));
226+ }
227+
228+ // Loop on process until it exits normally.
229+ do {
230+ $ status = proc_get_status ($ process );
231+ } while ($ status && $ status ['running ' ]);
232+ $ exitCode = $ status ['exitcode ' ] ?? -1 ;
233+ proc_close ($ process );
234+ if ($ exitCode !== 0 ) {
235+ $ this ->io ->writeError ($ this ->pluginErrorMessage ('installation process failed ' ));
236+ }
237+ }
238+
239+ /**
240+ * Return path to the CaptainHook configuration file
241+ *
242+ * @return void
243+ */
244+ private function detectConfiguration (): void
245+ {
246+ $ extra = $ this ->composer ->getPackage ()->getExtra ();
247+ $ this ->configuration = getcwd () . '/ ' . ($ extra ['captainhook ' ]['config ' ] ?? 'captainhook.json ' );
248+ }
249+
250+ /**
251+ * Search for the git repository to store the hooks in
252+
253+ * @return void
254+ * @throws \RuntimeException
255+ */
256+ private function detectGitDir (): void
257+ {
258+ $ path = getcwd ();
259+
260+ while (file_exists ($ path )) {
261+ $ possibleGitDir = $ path . '/.git ' ;
262+ if (is_dir ($ possibleGitDir )) {
263+ $ this ->gitDirectory = $ possibleGitDir ;
264+ return ;
265+ }
266+
267+ // if we checked the root directory already, break to prevent endless loop
268+ if ($ path === dirname ($ path )) {
269+ break ;
270+ }
271+
272+ $ path = \dirname ($ path );
273+ }
274+ throw new RuntimeException ($ this ->pluginErrorMessage ('git directory not found ' ));
275+ }
276+
277+ /**
278+ * Creates a nice formatted error message
279+ *
280+ * @param string $reason
281+ * @return string
282+ */
283+ private function pluginErrorMessage (string $ reason ): string
284+ {
285+ return 'Shiver me timbers! CaptainHook could not install yer git hooks! ( ' . $ reason . ') ' ;
286+ }
287+
288+ /**
289+ *
290+ */
291+ private function detectCaptainExecutable (): void
292+ {
293+ $ extra = $ this ->composer ->getPackage ()->getExtra ();
294+ if (isset ($ extra ['captainhook ' ]['exec ' ])) {
295+ $ this ->executable = $ extra ['captainhook ' ]['exec ' ];
296+ return ;
297+ }
298+
299+ $ this ->executable = (string ) $ this ->composer ->getConfig ()->get ('bin-dir ' ) . '/captainhook ' ;
116300 }
117301
118302 /**
119- * Checks if CaptainHook is installed properly
303+ * Check if the plugin is disabled
120304 *
121305 * @return bool
122306 */
123- private function isCaptainHookInstalled () : bool
307+ private function isPluginDisabled () : bool
124308 {
125- return class_exists ('\\CaptainHook \\App \\CH ' );
309+ $ extra = $ this ->composer ->getPackage ()->getExtra ();
310+ return (bool ) ($ extra ['captainhook ' ]['disable-plugin ' ] ?? false );
126311 }
127312}
0 commit comments