@artilingo/artiframe-cli 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/assets/banner.jpg +0 -0
- package/assets/profile.png +0 -0
- package/bin/artiframe.js +3 -2
- package/bin/artiframe.php +1 -1
- package/core-stubs/app/Database.php +1 -1
- package/core-stubs/app/DotEnv.php +1 -1
- package/core-stubs/app/R2Manager.php +1 -1
- package/core-stubs/bin/SystemMethod.php +292 -16
- package/core-stubs/bin/ViewMethod.php +146 -37
- package/core-stubs/docs/de.html +4419 -955
- package/core-stubs/docs/en.html +4419 -956
- package/core-stubs/docs/es.html +4419 -951
- package/core-stubs/docs/fr.html +4101 -536
- package/core-stubs/docs/tr.html +4978 -955
- package/core-stubs/stubs/service/image.stub +334 -0
- package/core-stubs/stubs/service/iyzico.stub +637 -0
- package/core-stubs/stubs/service/jwt.stub +312 -0
- package/core-stubs/stubs/service/pusher.stub +232 -0
- package/core-stubs/stubs/service/redis.stub +361 -0
- package/core-stubs/stubs/service/s3.stub +377 -0
- package/core-stubs/stubs/service/sentry.stub +139 -0
- package/core-stubs/stubs/service/stripe.stub +526 -0
- package/core-stubs/stubs/service/twilio.stub +361 -0
- package/package.json +4 -4
- package/src/App.php +69 -0
- package/src/Commands/AddCommand.php +392 -0
- package/src/Commands/GoCommand.php +48 -0
- package/src/Commands/ListCommand.php +67 -0
- package/src/Commands/NewProjectCommand.php +184 -30
- package/src/Commands/RemoveCommand.php +121 -0
- package/src/Commands/ServeCommand.php +71 -0
- package/src/Commands/ShowCommand.php +24 -0
- package/src/Lang/de.php +56 -0
- package/src/Lang/en.php +56 -0
- package/src/Lang/es.php +56 -0
- package/src/Lang/fr.php +56 -0
- package/src/Lang/tr.php +56 -0
|
@@ -56,6 +56,35 @@ class NewProjectCommand
|
|
|
56
56
|
echo " " . $this->translator->get('LOCATION_LABEL') . " : " . $targetDir . PHP_EOL;
|
|
57
57
|
echo PHP_EOL;
|
|
58
58
|
|
|
59
|
+
// Soru sor: Package Name, Author, Homepage vb.
|
|
60
|
+
$cleanProjectName = strtolower(preg_replace('/[^a-zA-Z0-9_-]/', '', $projectName));
|
|
61
|
+
if (empty($cleanProjectName)) $cleanProjectName = 'project';
|
|
62
|
+
$defaultPackage = 'artiframe/' . $cleanProjectName;
|
|
63
|
+
|
|
64
|
+
$packageName = '';
|
|
65
|
+
while (!preg_match('/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$/', $packageName)) {
|
|
66
|
+
$packageName = $this->ask('Package name (e.g. vendor/project)', $defaultPackage);
|
|
67
|
+
|
|
68
|
+
if (strpos($packageName, '/') === false && trim($packageName) !== '') {
|
|
69
|
+
$packageName = 'artiframe/' . trim($packageName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!preg_match('/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$/', $packageName)) {
|
|
73
|
+
echo " ❌ " . $this->translator->get('INVALID_PACKAGE_NAME', ['default' => 'vendor/project']) . "\n";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
$description = $this->ask('Description', 'ArtiFrame Core PHP Application');
|
|
77
|
+
$authorName = $this->ask('Author name', 'Artilingo');
|
|
78
|
+
$authorHomepage = $this->ask('Author homepage', 'https://artilingo.com');
|
|
79
|
+
$type = $this->askChoice('Type', ['project', 'library', 'composer-plugin'], 'project');
|
|
80
|
+
|
|
81
|
+
$licenses = ['AGPL-3.0-or-later', 'MIT', 'Apache-2.0', 'GPL-2.0-or-later', 'GPL-3.0-or-later', 'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'proprietary', 'none'];
|
|
82
|
+
$license = $this->askChoice('License', $licenses, 'AGPL-3.0-or-later');
|
|
83
|
+
|
|
84
|
+
if ($license === 'none') {
|
|
85
|
+
$license = '';
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
// Toplam adım sayısını hesapla
|
|
60
89
|
$coreStubs = \ARTIFRAME_CLI_ROOT . '/core-stubs';
|
|
61
90
|
$stubCount = $this->countFiles($coreStubs);
|
|
@@ -104,7 +133,7 @@ class NewProjectCommand
|
|
|
104
133
|
|
|
105
134
|
// ── FAZ 3: DİNAMİK DOSYALAR ──────────────────────────
|
|
106
135
|
$this->printPhaseHeader(3, 4, $this->translator->get('PHASE_FILES'), $dynamicCount);
|
|
107
|
-
$this->createDynamicFiles($targetDir);
|
|
136
|
+
$this->createDynamicFiles($targetDir, $packageName, $description, $authorName, $authorHomepage, $type, $license);
|
|
108
137
|
$this->finishPhase();
|
|
109
138
|
|
|
110
139
|
// ── FAZ 4: COMPOSER ──────────────────────────────────
|
|
@@ -129,9 +158,59 @@ class NewProjectCommand
|
|
|
129
158
|
$this->printDirectoryTree($targetDir, $projectName);
|
|
130
159
|
|
|
131
160
|
echo PHP_EOL;
|
|
132
|
-
echo " " . $this->translator->get('
|
|
133
|
-
echo
|
|
134
|
-
|
|
161
|
+
echo " " . $this->translator->get('NEXT_STEPS_EDIT_ENV') . PHP_EOL;
|
|
162
|
+
echo PHP_EOL;
|
|
163
|
+
|
|
164
|
+
// Yeni terminal penceresini proje dizininde aç ve artiframe başlat
|
|
165
|
+
$this->launchProjectTerminal($targetDir);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Proje dizininde yeni bir terminal penceresi açar ve artiframe'i başlatır.
|
|
170
|
+
* Windows, macOS ve Linux'u otomatik algılar.
|
|
171
|
+
*/
|
|
172
|
+
private function launchProjectTerminal(string $targetDir): void
|
|
173
|
+
{
|
|
174
|
+
$escapedDir = escapeshellarg($targetDir);
|
|
175
|
+
|
|
176
|
+
if (PHP_OS_FAMILY === 'Windows') {
|
|
177
|
+
// Windows: Yeni bir PowerShell penceresi aç, proje dizinine git, artiframe başlat
|
|
178
|
+
$cmd = 'start powershell -NoExit -Command "Set-Location ' . $escapedDir . '; artiframe"';
|
|
179
|
+
pclose(popen($cmd, 'r'));
|
|
180
|
+
} elseif (PHP_OS_FAMILY === 'Darwin') {
|
|
181
|
+
// macOS: Terminal.app ile yeni pencere
|
|
182
|
+
$script = 'tell application "Terminal" to do script "cd ' . $escapedDir . ' && artiframe"';
|
|
183
|
+
exec('osascript -e ' . escapeshellarg($script) . ' &');
|
|
184
|
+
} else {
|
|
185
|
+
// Linux: Yaygın terminal emülatörlerini dene
|
|
186
|
+
$terminals = [
|
|
187
|
+
'gnome-terminal' => 'gnome-terminal --working-directory=' . $escapedDir . ' -- bash -c "artiframe; exec bash"',
|
|
188
|
+
'konsole' => 'konsole --workdir ' . $escapedDir . ' -e bash -c "artiframe; exec bash"',
|
|
189
|
+
'xfce4-terminal' => 'xfce4-terminal --working-directory=' . $escapedDir . ' -e "bash -c \'artiframe; exec bash\'"',
|
|
190
|
+
'xterm' => 'xterm -e "cd ' . $escapedDir . ' && artiframe && bash"',
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
$launched = false;
|
|
194
|
+
foreach ($terminals as $bin => $termCmd) {
|
|
195
|
+
$check = trim(shell_exec('which ' . $bin . ' 2>/dev/null') ?? '');
|
|
196
|
+
if ($check !== '') {
|
|
197
|
+
exec($termCmd . ' &');
|
|
198
|
+
$launched = true;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!$launched) {
|
|
204
|
+
// Hiçbir terminal bulunamadıysa fallback: sadece yolu göster
|
|
205
|
+
echo " ⚠️ " . $this->translator->get('TERMINAL_FALLBACK') . PHP_EOL;
|
|
206
|
+
echo " cd " . basename($targetDir) . PHP_EOL;
|
|
207
|
+
echo PHP_EOL;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
echo " 🖥️ " . $this->translator->get('TERMINAL_OPENED') . PHP_EOL;
|
|
213
|
+
echo " ✅ " . $this->translator->get('TERMINAL_CLOSE_OLD') . PHP_EOL;
|
|
135
214
|
echo PHP_EOL;
|
|
136
215
|
}
|
|
137
216
|
|
|
@@ -325,23 +404,21 @@ class NewProjectCommand
|
|
|
325
404
|
|
|
326
405
|
// ─── Dinamik Dosya Üretici ────────────────────────────────
|
|
327
406
|
|
|
328
|
-
private function createDynamicFiles(string $targetDir): void
|
|
407
|
+
private function createDynamicFiles(string $targetDir, string $packageName, string $description, string $authorName, string $authorHomepage, string $type, string $license): void
|
|
329
408
|
{
|
|
330
|
-
$licenseHeader = "<?php\n/**\n * ArtiFrame Core Engine\n *\n * @package ArtiFrame\n * @author Artilingo\n * @license AGPLv3 (Attribution-ShareAlike Required)\n * @link https://artiframe.
|
|
409
|
+
$licenseHeader = "<?php\n/**\n * ArtiFrame Core Engine\n *\n * @package ArtiFrame\n * @author Artilingo\n * @license AGPLv3 (Attribution-ShareAlike Required)\n * @link https://artiframe.artilingo.com\n *\n * NOTICE: This file is part of the ArtiFrame ecosystem.\n * Any derivative works or patches MUST retain this original copyright notice\n * and remain open-source under the AGPLv3 license.\n */\n";
|
|
410
|
+
|
|
411
|
+
$licenseLine = $license !== '' ? "\n \"license\": \"$license\"," : "";
|
|
331
412
|
|
|
332
413
|
// composer.json
|
|
333
414
|
$composerJson = <<<JSON
|
|
334
415
|
{
|
|
335
|
-
"name": "
|
|
336
|
-
"description": "
|
|
337
|
-
"type": "
|
|
338
|
-
"license": "AGPL-3.0-or-later",
|
|
416
|
+
"name": "$packageName",
|
|
417
|
+
"description": "$description",
|
|
418
|
+
"type": "$type",$licenseLine
|
|
339
419
|
"require": {
|
|
340
420
|
"php": ">=8.1",
|
|
341
|
-
"
|
|
342
|
-
"aws/aws-sdk-php": "^3.316",
|
|
343
|
-
"predis/predis": "^2.2",
|
|
344
|
-
"ramsey/uuid": "^4.7"
|
|
421
|
+
"predis/predis": "^2.2"
|
|
345
422
|
},
|
|
346
423
|
"autoload": {
|
|
347
424
|
"psr-4": {
|
|
@@ -352,8 +429,8 @@ class NewProjectCommand
|
|
|
352
429
|
},
|
|
353
430
|
"authors": [
|
|
354
431
|
{
|
|
355
|
-
"name": "
|
|
356
|
-
"homepage": "
|
|
432
|
+
"name": "$authorName",
|
|
433
|
+
"homepage": "$authorHomepage"
|
|
357
434
|
}
|
|
358
435
|
],
|
|
359
436
|
"config": {
|
|
@@ -439,7 +516,7 @@ JSON;
|
|
|
439
516
|
* @package ArtiFrame
|
|
440
517
|
* @author Artilingo
|
|
441
518
|
* @license AGPLv3 (Attribution-ShareAlike Required)
|
|
442
|
-
* @link https://artiframe.
|
|
519
|
+
* @link https://artiframe.artilingo.com
|
|
443
520
|
*/
|
|
444
521
|
|
|
445
522
|
namespace Src\Email;
|
|
@@ -505,7 +582,7 @@ EMAILPHP;
|
|
|
505
582
|
* @package ArtiFrame
|
|
506
583
|
* @author Artilingo
|
|
507
584
|
* @license AGPLv3 (Attribution-ShareAlike Required)
|
|
508
|
-
* @link https://artiframe.
|
|
585
|
+
* @link https://artiframe.artilingo.com
|
|
509
586
|
*/
|
|
510
587
|
|
|
511
588
|
namespace Src\Service;
|
|
@@ -568,27 +645,82 @@ REDISPHP;
|
|
|
568
645
|
// public/index.php — localized welcome page
|
|
569
646
|
$lang = $this->translator->getLang();
|
|
570
647
|
$docFile = [
|
|
571
|
-
'tr' => ['file' => 'kilavuz.html', 'installed' => '
|
|
572
|
-
'en' => ['file' => 'guide.html', 'installed' => '
|
|
573
|
-
'de' => ['file' => 'handbuch.html', 'installed' => '
|
|
574
|
-
'fr' => ['file' => 'guide_fr.html', 'installed' => '
|
|
575
|
-
'es' => ['file' => 'guia.html', 'installed' => '¡
|
|
648
|
+
'tr' => ['file' => 'kilavuz.html', 'installed' => 'Projeniz Başarıyla Oluşturuldu!', 'guide' => 'İlk adımlar için kılavuzu inceleyebilirsiniz:'],
|
|
649
|
+
'en' => ['file' => 'guide.html', 'installed' => 'Project Created Successfully!', 'guide' => 'Check out the guide for the first steps:'],
|
|
650
|
+
'de' => ['file' => 'handbuch.html', 'installed' => 'Projekt Erfolgreich Erstellt!', 'guide' => 'Für die ersten Schritte sehen Sie im Handbuch nach:'],
|
|
651
|
+
'fr' => ['file' => 'guide_fr.html', 'installed' => 'Projet Créé avec Succès !', 'guide' => 'Consultez le guide pour les premières étapes :'],
|
|
652
|
+
'es' => ['file' => 'guia.html', 'installed' => '¡Proyecto Creado Exitosamente!', 'guide' => 'Consulta la guía para los primeros pasos:'],
|
|
576
653
|
][$lang] ?? ['file' => 'guide.html', 'installed' => 'ArtiFrame Installed Successfully!', 'guide' => 'To get started, see'];
|
|
577
654
|
|
|
578
|
-
$indexContent = "<?php\nrequire_once __DIR__ . '/../app/ViewControl.php';\n
|
|
655
|
+
$indexContent = "<?php\nrequire_once __DIR__ . '/../app/ViewControl.php';\n"
|
|
656
|
+
. "if (isset(\$_GET['guide'])) {\n"
|
|
657
|
+
. " header('Content-Type: text/html; charset=utf-8');\n"
|
|
658
|
+
. " readfile(__DIR__ . '/../{$docFile['file']}');\n"
|
|
659
|
+
. " exit;\n"
|
|
660
|
+
. "}\n"
|
|
661
|
+
. "?>\n"
|
|
579
662
|
. "<!DOCTYPE html>\n"
|
|
580
663
|
. "<html lang=\"{$lang}\" data-theme=\"default\" data-mode=\"light\">\n"
|
|
581
664
|
. "<head>\n"
|
|
582
665
|
. " <?php require_once __DIR__ . '/includes/head.php'; ?>\n"
|
|
583
666
|
. " <title>ArtiFrame | Welcome</title>\n"
|
|
667
|
+
. " <style>\n"
|
|
668
|
+
. " body {\n"
|
|
669
|
+
. " margin: 0; padding: 0;\n"
|
|
670
|
+
. " font-family: 'Inter', system-ui, -apple-system, sans-serif;\n"
|
|
671
|
+
. " background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);\n"
|
|
672
|
+
. " height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center;\n"
|
|
673
|
+
. " }\n"
|
|
674
|
+
. " .card {\n"
|
|
675
|
+
. " background: rgba(255, 255, 255, 0.7);\n"
|
|
676
|
+
. " backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);\n"
|
|
677
|
+
. " border: 1px solid rgba(255,255,255,0.5); border-radius: 1.5rem;\n"
|
|
678
|
+
. " padding: 3.5rem 4rem; text-align: center;\n"
|
|
679
|
+
. " box-shadow: 0 20px 40px rgba(0,0,0,0.08);\n"
|
|
680
|
+
. " max-width: 500px; animation: floatIn 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);\n"
|
|
681
|
+
. " margin-bottom: 2rem;\n"
|
|
682
|
+
. " }\n"
|
|
683
|
+
. " @keyframes floatIn { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } }\n"
|
|
684
|
+
. " .logo svg { width: 120px; height: auto; margin-bottom: 1.5rem; filter: drop-shadow(0 8px 12px rgba(10, 152, 114, 0.25)); }\n"
|
|
685
|
+
. " h1 {\n"
|
|
686
|
+
. " font-size: 2.2rem; font-weight: 800; margin: 0 0 1rem 0;\n"
|
|
687
|
+
. " background: linear-gradient(to right, #099772, #0a9872);\n"
|
|
688
|
+
. " -webkit-background-clip: text; -webkit-text-fill-color: transparent;\n"
|
|
689
|
+
. " letter-spacing: -0.025em;\n"
|
|
690
|
+
. " }\n"
|
|
691
|
+
. " p { font-size: 1.15rem; color: #4b5563; margin-bottom: 2rem; line-height: 1.6; }\n"
|
|
692
|
+
. " .btn {\n"
|
|
693
|
+
. " display: inline-block; background: linear-gradient(to right, #099772, #0a9872);\n"
|
|
694
|
+
. " color: white; text-decoration: none; padding: 0.8rem 2.2rem;\n"
|
|
695
|
+
. " border-radius: 9999px; font-weight: 600; font-size: 1rem;\n"
|
|
696
|
+
. " transition: all 0.3s ease; box-shadow: 0 4px 14px 0 rgba(9, 151, 114, 0.39);\n"
|
|
697
|
+
. " }\n"
|
|
698
|
+
. " .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(9, 151, 114, 0.45); }\n"
|
|
699
|
+
. " .footer {\n"
|
|
700
|
+
. " display: flex; align-items: center; gap: 0.5rem;\n"
|
|
701
|
+
. " color: #6b7280; font-size: 0.875rem; font-weight: 500;\n"
|
|
702
|
+
. " opacity: 0.7; transition: opacity 0.3s ease; animation: fadeIn 1.5s ease;\n"
|
|
703
|
+
. " }\n"
|
|
704
|
+
. " @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 0.7; } }\n"
|
|
705
|
+
. " .footer:hover { opacity: 1; }\n"
|
|
706
|
+
. " .artilingo-logo svg { height: 24px; width: auto; }\n"
|
|
707
|
+
. " </style>\n"
|
|
584
708
|
. "</head>\n"
|
|
585
709
|
. "<body>\n"
|
|
586
|
-
. " <
|
|
587
|
-
. " <
|
|
588
|
-
. "
|
|
589
|
-
. " </
|
|
590
|
-
. " <
|
|
591
|
-
. "
|
|
710
|
+
. " <div class=\"card\">\n"
|
|
711
|
+
. " <div class=\"logo\">\n"
|
|
712
|
+
. " " . trim(preg_replace('/<\?xml.*?\?>\n?/', '', file_get_contents(__DIR__ . '/../../assets/logo.svg'))) . "\n"
|
|
713
|
+
. " </div>\n"
|
|
714
|
+
. " <h1>{$docFile['installed']}</h1>\n"
|
|
715
|
+
. " <p>{$docFile['guide']}</p>\n"
|
|
716
|
+
. " <a href=\"?guide\" class=\"btn\">{$docFile['file']}</a>\n"
|
|
717
|
+
. " </div>\n"
|
|
718
|
+
. " <div class=\"footer\">\n"
|
|
719
|
+
. " <span>Powered by</span>\n"
|
|
720
|
+
. " <div class=\"artilingo-logo\">\n"
|
|
721
|
+
. " " . trim(preg_replace('/<\?xml.*?\?>\n?/', '', file_get_contents(__DIR__ . '/../../public/assets/images/artilingo.svg'))) . "\n"
|
|
722
|
+
. " </div>\n"
|
|
723
|
+
. " </div>\n"
|
|
592
724
|
. "</body>\n"
|
|
593
725
|
. "</html>\n";
|
|
594
726
|
file_put_contents($targetDir . '/public/index.php', $indexContent);
|
|
@@ -630,4 +762,26 @@ MANIFEST;
|
|
|
630
762
|
$command = "cd " . escapeshellarg($targetDir) . " && composer install";
|
|
631
763
|
passthru($command);
|
|
632
764
|
}
|
|
765
|
+
|
|
766
|
+
// ─── Kullanıcı Girdisi ────────────────────────────────────
|
|
767
|
+
|
|
768
|
+
private function ask(string $question, string $default = ''): string
|
|
769
|
+
{
|
|
770
|
+
$defaultStr = $default !== '' ? " [$default]" : '';
|
|
771
|
+
echo " \033[38;5;81m?\033[0m $question$defaultStr: ";
|
|
772
|
+
$answer = trim(fgets(STDIN));
|
|
773
|
+
return $answer === '' ? $default : $answer;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private function askChoice(string $question, array $choices, string $default): string
|
|
777
|
+
{
|
|
778
|
+
$choicesStr = implode(', ', $choices);
|
|
779
|
+
while (true) {
|
|
780
|
+
$answer = $this->ask("$question ($choicesStr)", $default);
|
|
781
|
+
if (in_array($answer, $choices)) {
|
|
782
|
+
return $answer;
|
|
783
|
+
}
|
|
784
|
+
echo " \033[1;31m✖\033[0m Geçersiz seçim. Lütfen seçeneklerden birini girin.\n";
|
|
785
|
+
}
|
|
786
|
+
}
|
|
633
787
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
use ArtiFrame\Cli\Services\Safeguard;
|
|
6
|
+
|
|
7
|
+
class RemoveCommand
|
|
8
|
+
{
|
|
9
|
+
private Translator $translator;
|
|
10
|
+
private Safeguard $safeguard;
|
|
11
|
+
|
|
12
|
+
public function __construct(Translator $translator)
|
|
13
|
+
{
|
|
14
|
+
$this->translator = $translator;
|
|
15
|
+
$this->safeguard = new Safeguard($translator);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public function execute(array $args): void
|
|
19
|
+
{
|
|
20
|
+
$target = $args[0] ?? null;
|
|
21
|
+
|
|
22
|
+
if (!$target) {
|
|
23
|
+
echo "❌ " . $this->translator->get('REMOVE_TARGET_REQUIRED') . PHP_EOL;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$projectRoot = getcwd();
|
|
28
|
+
|
|
29
|
+
$targetPath = $target;
|
|
30
|
+
if (!file_exists($targetPath)) {
|
|
31
|
+
$targetPath = rtrim($projectRoot, '/') . '/' . ltrim($target, '/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!file_exists($targetPath) && !str_ends_with($targetPath, '.php')) {
|
|
35
|
+
$targetPath .= '.php';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!file_exists($targetPath) || is_dir($targetPath)) {
|
|
39
|
+
echo "❌ " . $this->translator->get('REMOVE_FILE_NOT_FOUND', ['path' => $target]) . PHP_EOL;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
echo "\n⚠️ " . $this->translator->get('REMOVE_CONFIRM_FILE', ['path' => $targetPath]) . " [y/N]: ";
|
|
44
|
+
$ans = trim(fgets(STDIN));
|
|
45
|
+
if (strtolower($ans) !== 'y') {
|
|
46
|
+
echo "Abort.\n";
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$normalizedPath = str_replace('\\', '/', $targetPath);
|
|
51
|
+
$isView = strpos($normalizedPath, '/public/') !== false && strpos($normalizedPath, '/public/api/') === false && strpos($normalizedPath, '/public/assets/') === false;
|
|
52
|
+
$isClass = strpos($normalizedPath, '/src/') !== false || strpos($normalizedPath, '/app/') !== false;
|
|
53
|
+
|
|
54
|
+
if ($isView) {
|
|
55
|
+
$relPath = str_replace(str_replace('\\', '/', $projectRoot) . '/public/', '', $normalizedPath);
|
|
56
|
+
$relPath = preg_replace('/\.php$/', '', $relPath);
|
|
57
|
+
|
|
58
|
+
$cssPath = $projectRoot . '/public/assets/css/' . $relPath . '.css';
|
|
59
|
+
$jsPath = $projectRoot . '/public/assets/js/' . $relPath . '.js';
|
|
60
|
+
|
|
61
|
+
$assetsFound = [];
|
|
62
|
+
if (file_exists($cssPath)) $assetsFound[] = $cssPath;
|
|
63
|
+
if (file_exists($jsPath)) $assetsFound[] = $jsPath;
|
|
64
|
+
|
|
65
|
+
if (!empty($assetsFound)) {
|
|
66
|
+
echo "\n⚠️ " . $this->translator->get('REMOVE_CONFIRM_ASSETS') . " [y/N]: ";
|
|
67
|
+
$ansAssets = trim(fgets(STDIN));
|
|
68
|
+
if (strtolower($ansAssets) === 'y') {
|
|
69
|
+
foreach ($assetsFound as $a) {
|
|
70
|
+
unlink($a);
|
|
71
|
+
echo " ✅ Deleted: " . $a . "\n";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} elseif ($isClass) {
|
|
76
|
+
$className = pathinfo($targetPath, PATHINFO_FILENAME);
|
|
77
|
+
$apiDir = $projectRoot . '/public/api';
|
|
78
|
+
|
|
79
|
+
if (is_dir($apiDir)) {
|
|
80
|
+
$usages = $this->searchClassUsages($apiDir, $className);
|
|
81
|
+
|
|
82
|
+
if (!empty($usages)) {
|
|
83
|
+
echo "\n🚨 " . $this->translator->get('REMOVE_FOUND_APIS', ['class' => $className]) . "\n";
|
|
84
|
+
foreach ($usages as $usage) {
|
|
85
|
+
echo " -> " . $usage['file'] . " (Lines: " . implode(', ', $usage['lines']) . ")\n";
|
|
86
|
+
}
|
|
87
|
+
echo "\n📌 " . $this->translator->get('REMOVE_API_WARNING') . "\n";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
unlink($targetPath);
|
|
93
|
+
echo "\n✅ " . $this->translator->get('REMOVE_SUCCESS', ['path' => $targetPath]) . PHP_EOL;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private function searchClassUsages(string $dir, string $className): array
|
|
97
|
+
{
|
|
98
|
+
$usages = [];
|
|
99
|
+
$items = new \RecursiveIteratorIterator(
|
|
100
|
+
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
|
|
101
|
+
);
|
|
102
|
+
foreach ($items as $item) {
|
|
103
|
+
if ($item->isFile() && $item->getExtension() === 'php') {
|
|
104
|
+
$lines = file($item->getRealPath());
|
|
105
|
+
$foundLines = [];
|
|
106
|
+
foreach ($lines as $index => $line) {
|
|
107
|
+
if (strpos($line, $className) !== false) {
|
|
108
|
+
$foundLines[] = $index + 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!empty($foundLines)) {
|
|
112
|
+
$usages[] = [
|
|
113
|
+
'file' => $item->getRealPath(),
|
|
114
|
+
'lines' => $foundLines
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return $usages;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
|
|
6
|
+
class ServeCommand
|
|
7
|
+
{
|
|
8
|
+
private Translator $translator;
|
|
9
|
+
|
|
10
|
+
public function __construct(Translator $translator)
|
|
11
|
+
{
|
|
12
|
+
$this->translator = $translator;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public function execute(array $args): void
|
|
16
|
+
{
|
|
17
|
+
$projectRoot = getcwd();
|
|
18
|
+
$publicDir = $projectRoot . '/public';
|
|
19
|
+
|
|
20
|
+
if (!is_dir($publicDir) || !file_exists($publicDir . '/index.php')) {
|
|
21
|
+
echo "
|
|
22
|
+
❌ " . $this->translator->get('SERVE_NOT_PROJECT') . "
|
|
23
|
+
|
|
24
|
+
";
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$host = 'localhost';
|
|
29
|
+
$port = '8000';
|
|
30
|
+
|
|
31
|
+
if (isset($args[0]) && is_numeric($args[0])) {
|
|
32
|
+
$port = $args[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$url = "http://{$host}:{$port}";
|
|
36
|
+
|
|
37
|
+
echo "
|
|
38
|
+
🚀 " . $this->translator->get('SERVE_STARTING', ['url' => $url]) . "
|
|
39
|
+
";
|
|
40
|
+
echo " " . $this->translator->get('SERVE_STOP_INFO') . "
|
|
41
|
+
|
|
42
|
+
";
|
|
43
|
+
|
|
44
|
+
$os = PHP_OS_FAMILY;
|
|
45
|
+
|
|
46
|
+
// Command to start PHP built-in server
|
|
47
|
+
$phpCmd = sprintf('php -S %s:%s -t %s', $host, $port, escapeshellarg($publicDir));
|
|
48
|
+
|
|
49
|
+
if ($os === 'Windows') {
|
|
50
|
+
// Open Browser
|
|
51
|
+
pclose(popen("start {$url}", "r"));
|
|
52
|
+
// Start Server in new visible CMD window
|
|
53
|
+
pclose(popen("start \"ArtiFrame Server\" cmd /k \"{$phpCmd}\"", "r"));
|
|
54
|
+
} elseif ($os === 'Darwin') {
|
|
55
|
+
// Open Browser
|
|
56
|
+
exec("open {$url} > /dev/null 2>&1 &");
|
|
57
|
+
// Start Server in new Terminal window
|
|
58
|
+
$appleScript = 'tell app "Terminal" to do script "' . escapeshellcmd($phpCmd) . '"';
|
|
59
|
+
exec("osascript -e " . escapeshellarg($appleScript));
|
|
60
|
+
} else {
|
|
61
|
+
// Open Browser
|
|
62
|
+
exec("xdg-open {$url} > /dev/null 2>&1 &");
|
|
63
|
+
// Linux: Try common terminals, fallback to background process
|
|
64
|
+
$linuxCmd = sprintf(
|
|
65
|
+
'gnome-terminal -- bash -c "%s" || xterm -e "%s" || konsole -e "%s" || %s > /dev/null 2>&1 &',
|
|
66
|
+
$phpCmd, $phpCmd, $phpCmd, $phpCmd
|
|
67
|
+
);
|
|
68
|
+
exec($linuxCmd);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
|
|
6
|
+
class ShowCommand
|
|
7
|
+
{
|
|
8
|
+
private Translator $translator;
|
|
9
|
+
|
|
10
|
+
public function __construct(Translator $translator)
|
|
11
|
+
{
|
|
12
|
+
$this->translator = $translator;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public function execute(array $args): void
|
|
16
|
+
{
|
|
17
|
+
echo "
|
|
18
|
+
📍 " . $this->translator->get('SHOW_CURRENT_DIR') . "
|
|
19
|
+
";
|
|
20
|
+
echo " " . getcwd() . "
|
|
21
|
+
|
|
22
|
+
";
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/Lang/de.php
CHANGED
|
@@ -103,4 +103,60 @@ return [
|
|
|
103
103
|
'VERSION_UPDATE_SUCCESS' => '✅ Update abgeschlossen! Bitte Terminal neu starten.',
|
|
104
104
|
'VERSION_UPDATE_FAILED' => '❌ Update fehlgeschlagen.',
|
|
105
105
|
'HELP_CLI_VERSION_DESC' => 'CLI-Version anzeigen und nach Updates suchen.',
|
|
106
|
+
|
|
107
|
+
// Terminal Launch
|
|
108
|
+
'TERMINAL_OPENED' => 'Neues Terminal im Projektverzeichnis geöffnet.',
|
|
109
|
+
'TERMINAL_CLOSE_OLD' => 'Sie können dieses Fenster schließen.',
|
|
110
|
+
'TERMINAL_FALLBACK' => 'Terminal konnte nicht automatisch geöffnet werden. Bitte navigieren Sie zum Projektverzeichnis:',
|
|
111
|
+
|
|
112
|
+
// AddCommand
|
|
113
|
+
'ADD_MISSING_NAME' => 'Paketname nicht angegeben. Verwendung: add <paketname>',
|
|
114
|
+
'ADD_NOT_FOUND' => '":name" wurde nicht im ArtiFrame-Paketregister gefunden.',
|
|
115
|
+
'ADD_NO_PROJECT' => 'Kein ArtiFrame-Projekt in diesem Verzeichnis gefunden. (composer.json fehlt)',
|
|
116
|
+
'ADD_INSTALLING' => ':name wird installiert... (:composer)',
|
|
117
|
+
'ADD_FAILED' => 'Installation von :name fehlgeschlagen.',
|
|
118
|
+
'ADD_SUCCESS' => ':name erfolgreich installiert! (:composer)',
|
|
119
|
+
'ADD_SERVICE_NEEDED' => 'Service-Klasse muss erstellt werden: :path',
|
|
120
|
+
'ADD_LIST_TITLE' => 'Verfügbare Pakete',
|
|
121
|
+
'ADD_CAT_INTEGRATED' => 'Integriert (Composer + Service-Klasse)',
|
|
122
|
+
'ADD_CAT_DIRECT' => 'Direkt verwendbar (Nur Composer)',
|
|
123
|
+
'HELP_ADD_DESC' => 'Fügt ein ArtiFrame-genehmigtes Paket zum Projekt hinzu.',
|
|
124
|
+
'HELP_ADD_LIST' => 'Listet alle Pakete auf',
|
|
125
|
+
|
|
126
|
+
// AddCommand (Extended)
|
|
127
|
+
'ADD_ALREADY' => ':name ist bereits in diesem Projekt installiert.',
|
|
128
|
+
'ADD_SERVICE_CREATED' => 'Service-Klasse erstellt: :path',
|
|
129
|
+
'ADD_ENV_UPDATED' => '.env und .env.example Dateien aktualisiert.',
|
|
130
|
+
'ADD_EDIT_ENV' => 'Bitte füllen Sie die API-Schlüssel in Ihrer .env-Datei aus.',
|
|
131
|
+
'ADD_STUB_MISSING' => 'Service-Vorlage für :name nicht gefunden.',
|
|
132
|
+
'ADD_SERVICE_EXISTS' => 'Service-Datei existiert bereits: :path (übersprungen)',
|
|
133
|
+
|
|
134
|
+
// Show / List
|
|
135
|
+
'SHOW_CURRENT_DIR' => 'Aktuelles Arbeitsverzeichnis:',
|
|
136
|
+
'HELP_SHOW_DESC' => 'Zeigt das aktuelle Arbeitsverzeichnis an.',
|
|
137
|
+
'HELP_LIST_DESC' => 'Listet den Verzeichnisbaum des aktuellen Verzeichnisses auf.',
|
|
138
|
+
|
|
139
|
+
// Go
|
|
140
|
+
'GO_MISSING_DIR' => 'Bitte geben Sie das Verzeichnis an. (z.B. go public oder go back)',
|
|
141
|
+
'GO_NOT_FOUND' => 'Das angegebene Verzeichnis wurde nicht gefunden: :dir',
|
|
142
|
+
'GO_SUCCESS' => 'Verzeichnis erfolgreich gewechselt.',
|
|
143
|
+
'HELP_GO_DESC' => 'Ermöglicht den Wechsel des aktuellen Verzeichnisses.',
|
|
144
|
+
|
|
145
|
+
// Remove
|
|
146
|
+
'REMOVE_TARGET_REQUIRED' => 'Bitte geben Sie den Namen oder Pfad der zu entfernenden Datei an.',
|
|
147
|
+
'REMOVE_FILE_NOT_FOUND' => 'Die angegebene Datei wurde nicht gefunden: :path',
|
|
148
|
+
'REMOVE_CONFIRM_FILE' => 'Sie sind im Begriff, diese Datei DAUERHAFT zu löschen. Sind Sie sicher? :path',
|
|
149
|
+
'REMOVE_CONFIRM_ASSETS' => 'Sollen die mit dieser View verbundenen CSS- und JS-Dateien ebenfalls dauerhaft gelöscht werden?',
|
|
150
|
+
'REMOVE_FOUND_APIS' => 'ACHTUNG: Die gelöschte Klasse :class wird in diesen API-Dateien verwendet:',
|
|
151
|
+
'REMOVE_API_WARNING' => 'Sie müssen die entsprechenden Zeilen in den obigen API-Dateien manuell bearbeiten oder entfernen.',
|
|
152
|
+
'REMOVE_SUCCESS' => 'Datei erfolgreich gelöscht: :path',
|
|
153
|
+
'HELP_REMOVE_DESC' => 'Entfernt eine Datei (und bei Bestätigung ihre Abhängigkeiten) sicher.',
|
|
154
|
+
|
|
155
|
+
// Serve
|
|
156
|
+
'SERVE_NOT_PROJECT' => 'Dieses Verzeichnis ist kein gültiges ArtiFrame-Projekt (public/index.php fehlt).',
|
|
157
|
+
'SERVE_STARTING' => 'Entwicklungsserver wird gestartet: :url',
|
|
158
|
+
'SERVE_STOP_INFO' => 'Drücken Sie STRG+C, um den Server zu stoppen.',
|
|
159
|
+
'HELP_SERVE_DESC' => 'Startet den lokalen Entwicklungsserver.',
|
|
160
|
+
|
|
161
|
+
'INVALID_PACKAGE_NAME' => 'Ungültiger Paketname. Das Format sollte wie folgt aussehen: vendor/project',
|
|
106
162
|
];
|
package/src/Lang/en.php
CHANGED
|
@@ -103,4 +103,60 @@ return [
|
|
|
103
103
|
'VERSION_UPDATE_SUCCESS' => '✅ Update complete! Please restart your terminal.',
|
|
104
104
|
'VERSION_UPDATE_FAILED' => '❌ Update failed.',
|
|
105
105
|
'HELP_CLI_VERSION_DESC' => 'Show CLI version and check for updates.',
|
|
106
|
+
|
|
107
|
+
// Terminal Launch
|
|
108
|
+
'TERMINAL_OPENED' => 'New terminal opened in project directory.',
|
|
109
|
+
'TERMINAL_CLOSE_OLD' => 'You can close this window.',
|
|
110
|
+
'TERMINAL_FALLBACK' => 'Could not open terminal automatically. Please navigate to project directory:',
|
|
111
|
+
|
|
112
|
+
// AddCommand
|
|
113
|
+
'ADD_MISSING_NAME' => 'Package name not specified. Usage: add <package-name>',
|
|
114
|
+
'ADD_NOT_FOUND' => '":name" was not found in ArtiFrame package registry.',
|
|
115
|
+
'ADD_NO_PROJECT' => 'No ArtiFrame project found in this directory. (composer.json missing)',
|
|
116
|
+
'ADD_INSTALLING' => 'Installing :name... (:composer)',
|
|
117
|
+
'ADD_FAILED' => ':name installation failed.',
|
|
118
|
+
'ADD_SUCCESS' => ':name installed successfully! (:composer)',
|
|
119
|
+
'ADD_SERVICE_NEEDED' => 'Service class needs to be created: :path',
|
|
120
|
+
'ADD_LIST_TITLE' => 'Available Packages',
|
|
121
|
+
'ADD_CAT_INTEGRATED' => 'Integrated (Composer + Service Class)',
|
|
122
|
+
'ADD_CAT_DIRECT' => 'Direct Use (Composer Only)',
|
|
123
|
+
'HELP_ADD_DESC' => 'Adds an ArtiFrame-approved package to the project.',
|
|
124
|
+
'HELP_ADD_LIST' => 'Lists all packages',
|
|
125
|
+
|
|
126
|
+
// AddCommand (Extended)
|
|
127
|
+
'ADD_ALREADY' => ':name is already installed in this project.',
|
|
128
|
+
'ADD_SERVICE_CREATED' => 'Service class created: :path',
|
|
129
|
+
'ADD_ENV_UPDATED' => '.env and .env.example files updated.',
|
|
130
|
+
'ADD_EDIT_ENV' => 'Please fill in the API keys in your .env file.',
|
|
131
|
+
'ADD_STUB_MISSING' => 'Service template not found for :name.',
|
|
132
|
+
'ADD_SERVICE_EXISTS' => 'Service file already exists: :path (skipped)',
|
|
133
|
+
|
|
134
|
+
// Show / List
|
|
135
|
+
'SHOW_CURRENT_DIR' => 'Current Working Directory:',
|
|
136
|
+
'HELP_SHOW_DESC' => 'Shows the current working directory.',
|
|
137
|
+
'HELP_LIST_DESC' => 'Lists the directory tree of the current directory.',
|
|
138
|
+
|
|
139
|
+
// Go
|
|
140
|
+
'GO_MISSING_DIR' => 'Please specify the directory to go to. (e.g., go public or go back)',
|
|
141
|
+
'GO_NOT_FOUND' => 'The specified directory was not found: :dir',
|
|
142
|
+
'GO_SUCCESS' => 'Directory changed successfully.',
|
|
143
|
+
'HELP_GO_DESC' => 'Allows you to change the current directory.',
|
|
144
|
+
|
|
145
|
+
// Remove
|
|
146
|
+
'REMOVE_TARGET_REQUIRED' => 'Please specify the name or path of the file you want to remove.',
|
|
147
|
+
'REMOVE_FILE_NOT_FOUND' => 'The specified file was not found: :path',
|
|
148
|
+
'REMOVE_CONFIRM_FILE' => 'You are about to PERMANENTLY delete this file. Are you sure? :path',
|
|
149
|
+
'REMOVE_CONFIRM_ASSETS' => 'Should the CSS and JS files associated with this View also be permanently deleted?',
|
|
150
|
+
'REMOVE_FOUND_APIS' => 'WARNING: The deleted :class class is used in these API files:',
|
|
151
|
+
'REMOVE_API_WARNING' => 'You must manually edit or remove the relevant lines in the API files above.',
|
|
152
|
+
'REMOVE_SUCCESS' => 'File deleted successfully: :path',
|
|
153
|
+
'HELP_REMOVE_DESC' => 'Safely removes a file (and its dependencies if confirmed).',
|
|
154
|
+
|
|
155
|
+
// Serve
|
|
156
|
+
'SERVE_NOT_PROJECT' => 'This directory is not a valid ArtiFrame project (public/index.php missing).',
|
|
157
|
+
'SERVE_STARTING' => 'Starting development server at: :url',
|
|
158
|
+
'SERVE_STOP_INFO' => 'Press CTRL+C to stop the server.',
|
|
159
|
+
'HELP_SERVE_DESC' => 'Starts the local development server.',
|
|
160
|
+
|
|
161
|
+
'INVALID_PACKAGE_NAME' => 'Invalid package name. Format should look like: vendor/project',
|
|
106
162
|
];
|