Skip to content

Commit a6ad866

Browse files
Add non-interactive support and CLI options to ci4ms:setup
- Support passing setup parameters (admin details, database settings, site info) via CLI arguments to enable automated installations. - Introduce a non-interactive mode that skips prompts and confirmation when all required arguments are provided. - Allow the setup process to use existing .env values for database configuration in non-interactive mode. - Update the application version to 0.31.3.0.
1 parent d252ff9 commit a6ad866

1 file changed

Lines changed: 158 additions & 39 deletions

File tree

modules/Backend/Commands/Ci4msSetup.php

Lines changed: 158 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,31 @@ class Ci4msSetup extends BaseCommand
1111
protected $group = 'Ci4MS';
1212
protected $name = 'ci4ms:setup';
1313
protected $description = 'Runs the full CI4MS installation process via CLI.';
14-
protected $usage = 'php spark ci4ms:setup';
14+
protected $usage = 'php spark ci4ms:setup [options]';
15+
16+
protected $options = [
17+
'--fname' => 'Admin first name',
18+
'--sname' => 'Admin last name',
19+
'--email' => 'Admin email address',
20+
'--username' => 'Admin username',
21+
'--password' => 'Admin password',
22+
'--dbHost' => 'Database hostname (default: localhost)',
23+
'--dbName' => 'Database name',
24+
'--dbUser' => 'Database username',
25+
'--dbPass' => 'Database password',
26+
'--dbDriver' => 'Database driver (default: MySQLi)',
27+
'--dbPrefix' => 'Database table prefix (default: ci4ms_)',
28+
'--dbPort' => 'Database port (default: 3306)',
29+
'--siteName' => 'Site name',
30+
'--baseUrl' => 'Base URL (e.g. https://example.com)',
31+
'--slogan' => 'Site slogan (optional)',
32+
];
33+
34+
/**
35+
* Non-interactive modda mı çalışıyoruz?
36+
* Tüm zorunlu argümanlar CLI'dan verilmişse interaktif prompt atlanır.
37+
*/
38+
private bool $nonInteractive = false;
1539

1640
public function run(array $params)
1741
{
@@ -29,22 +53,33 @@ public function run(array $params)
2953
return;
3054
}
3155

56+
// ─────────────────────────────────────────────────────────────
57+
// CLI argümanlarını oku — non-interactive mod kontrolü
58+
// ─────────────────────────────────────────────────────────────
59+
$cliArgs = $this->parseCliOptions();
60+
$this->nonInteractive = $this->hasAllRequired($cliArgs);
61+
62+
if ($this->nonInteractive) {
63+
CLI::write(' Running in non-interactive mode...', 'light_gray');
64+
CLI::write('');
65+
}
66+
3267
// ─────────────────────────────────────────────────────────────
3368
// 1. KULLANICI BİLGİLERİ
3469
// ─────────────────────────────────────────────────────────────
3570
CLI::write('[ Step 1/6 ] Admin User Information', 'yellow');
3671
CLI::write('─────────────────────────────────────', 'dark_gray');
3772

38-
$name = $this->promptRequired('First Name');
39-
$surname = $this->promptRequired('Last Name');
40-
$email = $this->promptValidated('Email', function ($val) {
73+
$name = $cliArgs['fname'] ?? $this->promptRequired('First Name');
74+
$surname = $cliArgs['sname'] ?? $this->promptRequired('Last Name');
75+
$email = $cliArgs['email'] ?? $this->promptValidated('Email', function ($val) {
4176
return filter_var($val, FILTER_VALIDATE_EMAIL) ? null : 'Please enter a valid email address.';
4277
});
43-
$username = $this->promptValidated('Username (alphanumeric, 3-50 chars)', function ($val) {
78+
$username = $cliArgs['username'] ?? $this->promptValidated('Username (alphanumeric, 3-50 chars)', function ($val) {
4479
if (!preg_match('/^[a-zA-Z0-9]{3,50}$/', $val)) return 'Username must be alphanumeric, 3-50 characters.';
4580
return null;
4681
});
47-
$password = $this->promptSecret('Password (min 8 chars)', function ($val) {
82+
$password = $cliArgs['password'] ?? $this->promptSecret('Password (min 8 chars)', function ($val) {
4883
if (strlen($val) < 8) return 'Password must be at least 8 characters.';
4984
return null;
5085
});
@@ -56,22 +91,34 @@ public function run(array $params)
5691
CLI::write('[ Step 2/6 ] Database Configuration', 'yellow');
5792
CLI::write('─────────────────────────────────────', 'dark_gray');
5893

59-
$dbHost = CLI::prompt('DB Host', 'localhost');
60-
$dbName = $this->promptValidated('DB Name (alphanumeric/dash)', function ($val) {
61-
if (!preg_match('/^[a-zA-Z0-9_-]{1,100}$/', $val)) return 'DB name must be alphanumeric (max 100 chars).';
62-
return null;
63-
});
64-
$dbUsername = $this->promptValidated('DB Username', function ($val) {
65-
if (!preg_match('/^[a-zA-Z0-9_-]{1,100}$/', $val)) return 'DB username must be alphanumeric (max 100 chars).';
66-
return null;
67-
});
68-
$dbPassword = CLI::prompt('DB Password (leave blank if none)', '');
69-
$dbDriver = CLI::prompt('DB Driver', 'MySQLi');
70-
$dbPrefix = CLI::prompt('DB Prefix', 'ci4ms_');
71-
$dbPort = $this->promptValidated('DB Port', function ($val) {
72-
if (!ctype_digit($val) || (int)$val < 1 || (int)$val > 65535) return 'Port must be a number between 1-65535.';
73-
return null;
74-
}, '3306');
94+
if ($this->nonInteractive) {
95+
// Non-interactive: .env'deki mevcut DB ayarlarını kullan veya CLI argümanlarını al
96+
$dbHost = $cliArgs['dbHost'] ?? $this->getEnvValue('database.default.hostname', 'localhost');
97+
$dbName = $cliArgs['dbName'] ?? $this->getEnvValue('database.default.database', 'ci4ms');
98+
$dbUsername = $cliArgs['dbUser'] ?? $this->getEnvValue('database.default.username', 'root');
99+
$dbPassword = $cliArgs['dbPass'] ?? $this->getEnvValue('database.default.password', '');
100+
$dbDriver = $cliArgs['dbDriver'] ?? $this->getEnvValue('database.default.DBDriver', 'MySQLi');
101+
$dbPrefix = $cliArgs['dbPrefix'] ?? $this->getEnvValue('database.default.DBPrefix', 'ci4ms_');
102+
$dbPort = $cliArgs['dbPort'] ?? $this->getEnvValue('database.default.port', '3306');
103+
CLI::write(" Using DB: {$dbHost}:{$dbPort} / {$dbName}", 'light_gray');
104+
} else {
105+
$dbHost = CLI::prompt('DB Host', 'localhost');
106+
$dbName = $this->promptValidated('DB Name (alphanumeric/dash)', function ($val) {
107+
if (!preg_match('/^[a-zA-Z0-9_-]{1,100}$/', $val)) return 'DB name must be alphanumeric (max 100 chars).';
108+
return null;
109+
});
110+
$dbUsername = $this->promptValidated('DB Username', function ($val) {
111+
if (!preg_match('/^[a-zA-Z0-9_-]{1,100}$/', $val)) return 'DB username must be alphanumeric (max 100 chars).';
112+
return null;
113+
});
114+
$dbPassword = CLI::prompt('DB Password (leave blank if none)', '');
115+
$dbDriver = CLI::prompt('DB Driver', 'MySQLi');
116+
$dbPrefix = CLI::prompt('DB Prefix', 'ci4ms_');
117+
$dbPort = $this->promptValidated('DB Port', function ($val) {
118+
if (!ctype_digit($val) || (int)$val < 1 || (int)$val > 65535) return 'Port must be a number between 1-65535.';
119+
return null;
120+
}, '3306');
121+
}
75122

76123
// ─────────────────────────────────────────────────────────────
77124
// 3. SİTE BİLGİLERİ
@@ -80,20 +127,23 @@ public function run(array $params)
80127
CLI::write('[ Step 3/6 ] Site Information', 'yellow');
81128
CLI::write('─────────────────────────────────────', 'dark_gray');
82129

83-
$siteName = $this->promptValidated('Site Name', function ($val) {
130+
$siteName = $cliArgs['siteName'] ?? $this->promptValidated('Site Name', function ($val) {
84131
if (empty(trim($val)) || strlen($val) > 255) return 'Site name is required (max 255 chars).';
85132
if (preg_match('/[<>{}=]/', $val)) return 'Site name contains invalid characters.';
86133
return null;
87134
});
88-
$baseUrl = $this->promptValidated('Base URL (e.g. https://example.com)', function ($val) {
135+
$baseUrl = $cliArgs['baseUrl'] ?? $this->promptValidated('Base URL (e.g. https://example.com)', function ($val) {
89136
if (!filter_var($val, FILTER_VALIDATE_URL)) return 'Please enter a valid URL.';
90137
return null;
91138
});
92-
$slogan = CLI::prompt('Site Slogan (optional, leave blank to skip)', '');
93-
if ($slogan !== '') {
94-
while (strlen($slogan) > 255 || preg_match('/[<>{}=]/', $slogan)) {
95-
CLI::error('Slogan must be max 255 chars and cannot contain < > { } = characters.');
96-
$slogan = CLI::prompt('Site Slogan (optional)', '');
139+
$slogan = $cliArgs['slogan'] ?? '';
140+
if (!$this->nonInteractive && $slogan === '') {
141+
$slogan = CLI::prompt('Site Slogan (optional, leave blank to skip)', '');
142+
if ($slogan !== '') {
143+
while (strlen($slogan) > 255 || preg_match('/[<>{}=]/', $slogan)) {
144+
CLI::error('Slogan must be max 255 chars and cannot contain < > { } = characters.');
145+
$slogan = CLI::prompt('Site Slogan (optional)', '');
146+
}
97147
}
98148
}
99149

@@ -110,10 +160,12 @@ public function run(array $params)
110160
CLI::write(" Slogan : " . ($slogan !== '' ? $slogan : '(not set)'));
111161
CLI::write('');
112162

113-
$confirm = CLI::prompt('Everything looks correct? Proceed with installation?', ['y', 'n']);
114-
if (strtolower($confirm) !== 'y') {
115-
CLI::write('Setup cancelled by user.', 'red');
116-
return;
163+
if (!$this->nonInteractive) {
164+
$confirm = CLI::prompt('Everything looks correct? Proceed with installation?', ['y', 'n']);
165+
if (strtolower($confirm) !== 'y') {
166+
CLI::write('Setup cancelled by user.', 'red');
167+
return;
168+
}
117169
}
118170

119171
// ─────────────────────────────────────────────────────────────
@@ -122,9 +174,12 @@ public function run(array $params)
122174
CLI::write('');
123175
CLI::write('[ Step 4/6 ] Writing .env file...', 'yellow');
124176

125-
if (!$this->copyEnvFile()) {
126-
CLI::error('Could not copy env → .env. Aborting.');
127-
return;
177+
// Non-interactive modda .env zaten mevcutsa kopyalama atla
178+
if (!file_exists(ROOTPATH . '.env')) {
179+
if (!$this->copyEnvFile()) {
180+
CLI::error('Could not copy env → .env. Aborting.');
181+
return;
182+
}
128183
}
129184

130185
$updates = [
@@ -142,7 +197,7 @@ public function run(array $params)
142197
'cookie.path' => '\'/\'',
143198
'cookie.domain' => '\'\'',
144199
'cookie.secure' => 'false #Don\'t forget to set it to true when buying production mode.',
145-
'cookie.httponly' => 'true',
200+
'cookie.httponly' => 'true',
146201
'cookie.samesite' => '\'Lax\'',
147202
'cookie.raw' => 'false',
148203
'honeypot.hidden' => '\'true\'',
@@ -163,7 +218,7 @@ public function run(array $params)
163218
'app.supportedLocales' => '["ar","de","en","es","fr","hi","ja","pt","ru","tr","zh"]',
164219
'app.negotiateLocale' => 'true',
165220
'app.appTimezone' => '\'Europe/Istanbul\'',
166-
'app.version' => '0.31.2.0',
221+
'app.version' => '0.31.3.0',
167222
];
168223

169224
if (!$this->updateEnvSettings($updates)) {
@@ -243,6 +298,70 @@ public function run(array $params)
243298
CLI::write('');
244299
}
245300

301+
// ═════════════════════════════════════════════════════════════════
302+
// CLI OPTION PARSER
303+
// ═════════════════════════════════════════════════════════════════
304+
305+
/**
306+
* $_SERVER['argv'] üzerinden --key=value formatındaki argümanları parse et.
307+
* CI4'ün BaseCommand::$params dizisi bu formatta çalışmadığı için
308+
* doğrudan argv'den okuyoruz.
309+
*/
310+
private function parseCliOptions(): array
311+
{
312+
$options = [];
313+
$argv = $_SERVER['argv'] ?? [];
314+
315+
foreach ($argv as $arg) {
316+
if (str_starts_with($arg, '--') && str_contains($arg, '=')) {
317+
[$key, $value] = explode('=', substr($arg, 2), 2);
318+
$options[$key] = $value;
319+
}
320+
}
321+
322+
return $options;
323+
}
324+
325+
/**
326+
* Non-interactive mod için gerekli tüm zorunlu argümanlar var mı?
327+
*/
328+
private function hasAllRequired(array $args): bool
329+
{
330+
$required = ['fname', 'sname', 'email', 'username', 'password', 'siteName', 'baseUrl'];
331+
332+
foreach ($required as $key) {
333+
if (empty($args[$key] ?? '')) {
334+
return false;
335+
}
336+
}
337+
338+
return true;
339+
}
340+
341+
/**
342+
* Mevcut .env dosyasından bir değer oku
343+
*/
344+
private function getEnvValue(string $key, string $default = ''): string
345+
{
346+
// Önce $_ENV / $_SERVER dene (CI4 .env loader tarafından yüklenmiş olabilir)
347+
$envKey = str_replace('.', '_', $key);
348+
if (!empty($_ENV[$key])) return $_ENV[$key];
349+
if (!empty($_SERVER[$key])) return $_SERVER[$key];
350+
351+
// .env dosyasından doğrudan oku
352+
$envPath = ROOTPATH . '.env';
353+
if (!file_exists($envPath)) return $default;
354+
355+
$contents = file_get_contents($envPath);
356+
$pattern = '/^' . preg_quote($key, '/') . '\s*=\s*(.+)$/m';
357+
358+
if (preg_match($pattern, $contents, $matches)) {
359+
return trim($matches[1], " \t\n\r\0\x0B'\"");
360+
}
361+
362+
return $default;
363+
}
364+
246365
// ═════════════════════════════════════════════════════════════════
247366
// PRIVATE HELPERS
248367
// ═════════════════════════════════════════════════════════════════
@@ -363,7 +482,7 @@ private function writeRoutesFile(): bool
363482
}
364483

365484
// ═════════════════════════════════════════════════════════════════
366-
// CLI PROMPT HELPERS
485+
// CLI PROMPT HELPERS (sadece interaktif modda kullanılır)
367486
// ═════════════════════════════════════════════════════════════════
368487

369488
/**

0 commit comments

Comments
 (0)