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
/**
44
83
* Make sure the installer is executed after the autoloader is created
45
84
*
46
85
* @return array
47
86
*/
48
- public static function getSubscribedEvents () : array
87
+ public static function getSubscribedEvents (): array
49
88
{
50
89
return [
51
90
ScriptEvents::POST_AUTOLOAD_DUMP => 'installHooks '
@@ -59,21 +98,33 @@ public static function getSubscribedEvents() : array
59
98
* @return void
60
99
* @throws \Exception
61
100
*/
62
- public function installHooks (Event $ event ) : void
101
+ public function installHooks (Event $ event ): void
63
102
{
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 ;
69
108
}
70
109
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 .
76
117
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 .
77
128
'If you are uninstalling CaptainHook, we are sad seeing you go, ' .
78
129
'but we would appreciate your feedback on your experience. ' . PHP_EOL .
79
130
'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
82
133
);
83
134
return ;
84
135
}
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 ' ;
86
262
}
87
263
88
264
/**
89
- * Checks if CaptainHook is installed properly
265
+ * Check if the plugin is disabled
90
266
*
91
267
* @return bool
92
268
*/
93
- private function isCaptainHookInstalled () : bool
269
+ private function isPluginDisabled () : bool
94
270
{
95
- return class_exists ('\\CaptainHook \\App \\CH ' );
271
+ $ extra = $ this ->composer ->getPackage ()->getExtra ();
272
+ return (bool ) ($ extra ['captainhook ' ]['disable-plugin ' ] ?? false );
96
273
}
97
274
}
0 commit comments