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 /**
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