11
11
12
12
namespace CaptainHook \Plugin \Composer ;
13
13
14
- use CaptainHook \App \Composer \Cmd ;
15
14
use Composer \Composer ;
16
15
use Composer \EventDispatcher \EventSubscriberInterface ;
17
16
use Composer \IO \IOInterface ;
18
17
use Composer \Plugin \PluginInterface ;
19
18
use Composer \Script \Event ;
20
19
use Composer \Script \ScriptEvents ;
20
+ use RuntimeException ;
21
21
22
22
/**
23
23
* Class ComposerPlugin
28
28
*/
29
29
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
30
30
{
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
+
31
69
/**
32
70
* Activate the plugin
33
71
*
34
72
* @param \Composer\Composer $composer
35
73
* @param \Composer\IO\IOInterface $io
36
74
* @return void
37
75
*/
38
- public function activate (Composer $ composer , IOInterface $ io ) : void
76
+ public function activate (Composer $ composer , IOInterface $ io ): void
39
77
{
40
- // nothing to do here
78
+ $ this ->composer = $ composer ;
79
+ $ this ->io = $ io ;
41
80
}
42
81
43
82
/**
@@ -75,10 +114,11 @@ public function uninstall(Composer $composer, IOInterface $io)
75
114
*
76
115
* @return array
77
116
*/
78
- public static function getSubscribedEvents () : array
117
+ public static function getSubscribedEvents (): array
79
118
{
80
119
return [
81
- ScriptEvents::POST_AUTOLOAD_DUMP => 'installHooks '
120
+ ScriptEvents::POST_INSTALL_CMD => 'installHooks ' ,
121
+ ScriptEvents::POST_UPDATE_CMD => 'installHooks '
82
122
];
83
123
}
84
124
@@ -89,39 +129,184 @@ public static function getSubscribedEvents() : array
89
129
* @return void
90
130
* @throws \Exception
91
131
*/
92
- public function installHooks (Event $ event ) : void
132
+ public function installHooks (Event $ event ): void
93
133
{
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 ;
99
139
}
100
140
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 .
106
148
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 .
107
159
'If you are uninstalling CaptainHook, we are sad seeing you go, ' .
108
160
'but we would appreciate your feedback on your experience. ' . PHP_EOL .
109
161
'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
112
164
);
113
165
return ;
114
166
}
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 ' ;
116
300
}
117
301
118
302
/**
119
- * Checks if CaptainHook is installed properly
303
+ * Check if the plugin is disabled
120
304
*
121
305
* @return bool
122
306
*/
123
- private function isCaptainHookInstalled () : bool
307
+ private function isPluginDisabled () : bool
124
308
{
125
- return class_exists ('\\CaptainHook \\App \\CH ' );
309
+ $ extra = $ this ->composer ->getPackage ()->getExtra ();
310
+ return (bool ) ($ extra ['captainhook ' ]['disable-plugin ' ] ?? false );
126
311
}
127
312
}
0 commit comments