1+ <?php
2+
3+ declare (strict_types=1 );
4+ /**
5+ * This file is part of MineAdmin.
6+ *
7+ * @link https://www.mineadmin.com
8+ * @document https://doc.mineadmin.com
9+ * @contact root@imoi.cn
10+ * @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
11+ */
12+
13+ namespace App \Command ;
14+
15+ use Hyperf \Command \Annotation \Command as CommandAnnotation ;
16+ use Hyperf \Command \Command ;
17+ use Hyperf \Context \ApplicationContext ;
18+ use Hyperf \Contract \ConfigInterface ;
19+ use Hyperf \DbConnection \Db ;
20+ use Hyperf \Redis \RedisFactory ;
21+ use Throwable ;
22+
23+ #[CommandAnnotation]
24+ class InstallCommand extends Command
25+ {
26+ protected ?string $ name = 'mine:install ' ;
27+
28+ protected string $ description = 'Install MineAdmin data and configured extensions ' ;
29+
30+ public function handle (): int
31+ {
32+ $ this ->output ->title ('MineAdmin Install ' );
33+
34+ if (! $ this ->checkEnvironment ()) {
35+ return self ::FAILURE ;
36+ }
37+
38+ if (! $ this ->runRequiredCommand ('migrate ' )) {
39+ return self ::FAILURE ;
40+ }
41+
42+ if (! $ this ->runRequiredCommand ('db:seed ' )) {
43+ return self ::FAILURE ;
44+ }
45+
46+ if (! $ this ->installExtensions ()) {
47+ return self ::FAILURE ;
48+ }
49+
50+ $ this ->output ->success ('MineAdmin Install Success ' );
51+
52+ return self ::SUCCESS ;
53+ }
54+
55+ private function checkEnvironment (): bool
56+ {
57+ $ this ->output ->section ('Environment Check ' );
58+
59+ if (! file_exists (BASE_PATH . '/.env ' )) {
60+ $ this ->output ->error ('The .env file does not exist. Please create .env before installing MineAdmin. ' );
61+ return false ;
62+ }
63+ $ this ->output ->writeln ('<info>[OK]</info> .env file exists ' );
64+
65+ try {
66+ Db::select ('SELECT 1 ' );
67+ $ this ->output ->writeln ('<info>[OK]</info> Database connection is available ' );
68+ } catch (Throwable $ throwable ) {
69+ $ this ->output ->error ('Database connection failed: ' . $ throwable ->getMessage ());
70+ return false ;
71+ }
72+
73+ try {
74+ $ this ->redisFactory ()->get ('default ' )->ping ();
75+ $ this ->output ->writeln ('<info>[OK]</info> Redis connection is available ' );
76+ } catch (Throwable $ throwable ) {
77+ $ this ->output ->error ('Redis connection failed: ' . $ throwable ->getMessage ());
78+ return false ;
79+ }
80+
81+ return true ;
82+ }
83+
84+ private function runRequiredCommand (string $ command ): bool
85+ {
86+ $ this ->output ->section (sprintf ('Run %s ' , $ command ));
87+
88+ try {
89+ $ exitCode = $ this ->call ($ command );
90+ } catch (Throwable $ throwable ) {
91+ $ this ->output ->error (sprintf ('Command %s failed: %s ' , $ command , $ throwable ->getMessage ()));
92+ return false ;
93+ }
94+
95+ if ($ exitCode !== self ::SUCCESS ) {
96+ $ this ->output ->error (sprintf ('Command %s failed with exit code %d. ' , $ command , $ exitCode ));
97+ return false ;
98+ }
99+
100+ $ this ->output ->writeln (sprintf ('<info>[OK]</info> Command %s completed ' , $ command ));
101+
102+ return true ;
103+ }
104+
105+ private function installExtensions (): bool
106+ {
107+ $ this ->output ->section ('Install Extensions ' );
108+
109+ $ extensions = $ this ->config ()->get ('mine-extension.auto_install_list ' , []);
110+ if (! is_array ($ extensions )) {
111+ $ this ->output ->error ('Config mine-extension.auto_install_list must be an array. ' );
112+ return false ;
113+ }
114+
115+ if ($ extensions === []) {
116+ $ this ->output ->writeln ('<comment>[SKIP]</comment> auto_install_list is empty ' );
117+ return true ;
118+ }
119+
120+ $ failed = [];
121+ foreach ($ extensions as $ key => $ extension ) {
122+ $ path = $ this ->normalizeExtensionPath ($ extension , $ key );
123+ if ($ path === null ) {
124+ $ this ->output ->warning (sprintf ('Skip invalid extension config item: %s ' , (string ) $ key ));
125+ continue ;
126+ }
127+
128+ $ this ->output ->writeln (sprintf ('<comment>[INSTALL]</comment> %s ' , $ path ));
129+ if (! $ this ->installExtension ($ path )) {
130+ $ failed [] = $ path ;
131+ }
132+ }
133+
134+ if ($ failed !== []) {
135+ $ this ->output ->warning (sprintf (
136+ 'The following extensions failed and were skipped: %s ' ,
137+ implode (', ' , $ failed )
138+ ));
139+ }
140+
141+ return true ;
142+ }
143+
144+ private function installExtension (string $ path ): bool
145+ {
146+ try {
147+ $ exitCode = $ this ->call ('mine-extension:install ' , [
148+ 'path ' => $ path ,
149+ '--yes ' => true ,
150+ ]);
151+ } catch (Throwable $ throwable ) {
152+ $ this ->output ->error (sprintf (
153+ 'Plugin %s install failed: %s. Skip it and continue. ' ,
154+ $ path ,
155+ $ throwable ->getMessage ()
156+ ));
157+ return false ;
158+ }
159+
160+ if ($ exitCode !== self ::SUCCESS ) {
161+ $ this ->output ->error (sprintf (
162+ 'Plugin %s install failed with exit code %d. Skip it and continue. ' ,
163+ $ path ,
164+ $ exitCode
165+ ));
166+ return false ;
167+ }
168+
169+ $ this ->output ->writeln (sprintf ('<info>[OK]</info> Plugin %s installed ' , $ path ));
170+
171+ return true ;
172+ }
173+
174+ private function normalizeExtensionPath (mixed $ extension , int |string $ key ): ?string
175+ {
176+ if (is_string ($ extension ) || is_numeric ($ extension )) {
177+ $ path = trim ((string ) $ extension );
178+ return $ path === '' ? null : $ path ;
179+ }
180+
181+ if (is_array ($ extension ) && isset ($ extension ['path ' ]) && is_string ($ extension ['path ' ])) {
182+ $ path = trim ($ extension ['path ' ]);
183+ return $ path === '' ? null : $ path ;
184+ }
185+
186+ if (is_string ($ key ) && ($ extension === true || $ extension === null )) {
187+ $ path = trim ($ key );
188+ return $ path === '' ? null : $ path ;
189+ }
190+
191+ return null ;
192+ }
193+
194+ private function config (): ConfigInterface
195+ {
196+ return ApplicationContext::getContainer ()->get (ConfigInterface::class);
197+ }
198+
199+ private function redisFactory (): RedisFactory
200+ {
201+ return ApplicationContext::getContainer ()->get (RedisFactory::class);
202+ }
203+ }
0 commit comments