@artilingo/artiframe-cli 1.0.9 → 1.1.0

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/bin/artiframe.php +1 -1
  3. package/core-stubs/app/Database.php +1 -1
  4. package/core-stubs/app/DotEnv.php +1 -1
  5. package/core-stubs/app/R2Manager.php +1 -1
  6. package/core-stubs/bin/SystemMethod.php +292 -16
  7. package/core-stubs/bin/ViewMethod.php +146 -37
  8. package/core-stubs/docs/de.html +4419 -955
  9. package/core-stubs/docs/en.html +4419 -956
  10. package/core-stubs/docs/es.html +4419 -951
  11. package/core-stubs/docs/fr.html +4101 -536
  12. package/core-stubs/docs/tr.html +4978 -955
  13. package/core-stubs/stubs/service/image.stub +334 -0
  14. package/core-stubs/stubs/service/iyzico.stub +637 -0
  15. package/core-stubs/stubs/service/jwt.stub +312 -0
  16. package/core-stubs/stubs/service/pusher.stub +232 -0
  17. package/core-stubs/stubs/service/redis.stub +361 -0
  18. package/core-stubs/stubs/service/s3.stub +377 -0
  19. package/core-stubs/stubs/service/sentry.stub +139 -0
  20. package/core-stubs/stubs/service/stripe.stub +526 -0
  21. package/core-stubs/stubs/service/twilio.stub +361 -0
  22. package/package.json +4 -4
  23. package/src/App.php +69 -0
  24. package/src/Commands/AddCommand.php +392 -0
  25. package/src/Commands/GoCommand.php +48 -0
  26. package/src/Commands/ListCommand.php +67 -0
  27. package/src/Commands/NewProjectCommand.php +184 -30
  28. package/src/Commands/RemoveCommand.php +121 -0
  29. package/src/Commands/ServeCommand.php +71 -0
  30. package/src/Commands/ShowCommand.php +24 -0
  31. package/src/Lang/de.php +56 -0
  32. package/src/Lang/en.php +56 -0
  33. package/src/Lang/es.php +56 -0
  34. package/src/Lang/fr.php +56 -0
  35. 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('NEXT_STEPS') . PHP_EOL;
133
- echo " cd " . $projectName . PHP_EOL;
134
- echo " " . $this->translator->get('NEXT_STEPS_EDIT_ENV') . PHP_EOL;
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.org\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";
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": "artiframe/project",
336
- "description": "ArtiFrame Core PHP Application",
337
- "type": "project",
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
- "phpmailer/phpmailer": "^6.9",
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": "Artilingo",
356
- "homepage": "https://artilingo.com"
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.org
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.org
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' => 'ArtiFrame Başarıyla Kuruldu!', 'guide' => 'Başlamak için'],
572
- 'en' => ['file' => 'guide.html', 'installed' => 'ArtiFrame Installed Successfully!', 'guide' => 'To get started, see'],
573
- 'de' => ['file' => 'handbuch.html', 'installed' => 'ArtiFrame Erfolgreich Installiert!', 'guide' => 'Für den Einstieg, siehe'],
574
- 'fr' => ['file' => 'guide_fr.html', 'installed' => 'ArtiFrame Installé avec Succès !', 'guide' => 'Pour commencer, consultez'],
575
- 'es' => ['file' => 'guia.html', 'installed' => '¡ArtiFrame Instalado Exitosamente!', 'guide' => 'Para comenzar, vea'],
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?>\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
- . " <main style=\"display:flex;justify-content:center;align-items:center;height:100vh;flex-direction:column;font-family:'Inter',sans-serif;\">\n"
587
- . " <h1 style=\"background:linear-gradient(to right,#3b82f6,#8b5cf6);-webkit-background-clip:text;-webkit-text-fill-color:transparent;\">\n"
588
- . " {$docFile['installed']}\n"
589
- . " </h1>\n"
590
- . " <p>{$docFile['guide']} <a href=\"/{$docFile['file']}\">{$docFile['file']}</a>.</p>\n"
591
- . " </main>\n"
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
  ];