@artilingo/artiframe-cli 1.0.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.
- package/assets/logo.png +0 -0
- package/assets/logo.svg +22 -0
- package/assets/logo.webp +0 -0
- package/assets/logo@0.5x.png +0 -0
- package/assets/logo@0.75x.png +0 -0
- package/assets/logo@1.5x.png +0 -0
- package/assets/logo@2x.png +0 -0
- package/assets/logo@3x.png +0 -0
- package/assets/logo@4x.png +0 -0
- package/bin/artiframe.js +57 -0
- package/bin/artiframe.php +45 -0
- package/core-stubs/app/ApiControl.php +81 -0
- package/core-stubs/app/Database.php +69 -0
- package/core-stubs/app/DotEnv.php +63 -0
- package/core-stubs/app/R2Manager.php +130 -0
- package/core-stubs/app/ViewControl.php +24 -0
- package/core-stubs/bin/SystemMethod.php +271 -0
- package/core-stubs/bin/ViewMethod.php +354 -0
- package/core-stubs/docs/de.html +951 -0
- package/core-stubs/docs/en.html +952 -0
- package/core-stubs/docs/es.html +947 -0
- package/core-stubs/docs/fr.html +850 -0
- package/core-stubs/docs/tr.html +951 -0
- package/core-stubs/public/assets/css/components/footer.css +0 -0
- package/core-stubs/public/assets/css/components/header.css +0 -0
- package/core-stubs/public/assets/css/components/mobilenav.css +0 -0
- package/core-stubs/public/assets/css/components/sidebar.css +0 -0
- package/core-stubs/public/assets/css/components/theme-modal.css +0 -0
- package/core-stubs/public/assets/css/root/app.css +61 -0
- package/core-stubs/public/assets/js/components/header.js +0 -0
- package/core-stubs/public/assets/js/components/mobilenav.js +0 -0
- package/core-stubs/public/assets/js/components/sidebar.js +0 -0
- package/core-stubs/public/assets/js/components/theme-modal.js +0 -0
- package/core-stubs/public/assets/js/root/app.js +30 -0
- package/core-stubs/public/includes/footer.php +5 -0
- package/core-stubs/public/includes/head.php +26 -0
- package/core-stubs/public/includes/header.php +5 -0
- package/core-stubs/public/includes/mobilenav.php +5 -0
- package/core-stubs/public/includes/sidebar.php +5 -0
- package/core-stubs/public/includes/theme-modal.php +5 -0
- package/core-stubs/readme/de.md +29 -0
- package/core-stubs/readme/en.md +29 -0
- package/core-stubs/readme/es.md +29 -0
- package/core-stubs/readme/fr.md +29 -0
- package/core-stubs/readme/tr.md +29 -0
- package/package.json +49 -0
- package/scripts/postinstall.js +21 -0
- package/src/App.php +266 -0
- package/src/Commands/MakeApiCommand.php +71 -0
- package/src/Commands/MakeClassCommand.php +88 -0
- package/src/Commands/MakeViewCommand.php +95 -0
- package/src/Commands/NewProjectCommand.php +633 -0
- package/src/Commands/VersionCommand.php +92 -0
- package/src/Lang/de.php +53 -0
- package/src/Lang/en.php +53 -0
- package/src/Lang/es.php +53 -0
- package/src/Lang/fr.php +53 -0
- package/src/Lang/tr.php +53 -0
- package/src/Services/Safeguard.php +48 -0
- package/src/Services/Translator.php +52 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
use ArtiFrame\Cli\Services\Safeguard;
|
|
6
|
+
|
|
7
|
+
class NewProjectCommand
|
|
8
|
+
{
|
|
9
|
+
private Translator $translator;
|
|
10
|
+
private Safeguard $safeguard;
|
|
11
|
+
|
|
12
|
+
// İlerleme takibi
|
|
13
|
+
private int $totalSteps = 0;
|
|
14
|
+
private int $currentStep = 0;
|
|
15
|
+
|
|
16
|
+
public function __construct(Translator $translator)
|
|
17
|
+
{
|
|
18
|
+
$this->translator = $translator;
|
|
19
|
+
$this->safeguard = new Safeguard($translator);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function execute(array $args): void
|
|
23
|
+
{
|
|
24
|
+
$targetPath = $args[0] ?? null;
|
|
25
|
+
|
|
26
|
+
if (!$targetPath) {
|
|
27
|
+
echo $this->translator->get('DIR_REQUIRED_ERROR') . PHP_EOL;
|
|
28
|
+
exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 1. Dizin çözümleme
|
|
32
|
+
$targetDir = $this->resolvePath($targetPath);
|
|
33
|
+
$projectName = basename($targetDir);
|
|
34
|
+
|
|
35
|
+
// 2. Safeguard Kontrolü
|
|
36
|
+
if (!$this->safeguard->checkTarget($targetDir)) {
|
|
37
|
+
exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Composer Kontrolü
|
|
41
|
+
if (!$this->isComposerInstalled()) {
|
|
42
|
+
echo PHP_EOL . "❌ " . $this->translator->get('COMPOSER_MISSING_TITLE') . PHP_EOL;
|
|
43
|
+
echo str_repeat("-", 50) . PHP_EOL;
|
|
44
|
+
echo $this->translator->get('COMPOSER_MISSING_BODY') . PHP_EOL;
|
|
45
|
+
exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Başlık Ekranı ────────────────────────────────────
|
|
49
|
+
echo PHP_EOL;
|
|
50
|
+
$builderTitle = $this->translator->get('PROJECT_BUILDER_TITLE');
|
|
51
|
+
echo " ╔══════════════════════════════════════════════════╗" . PHP_EOL;
|
|
52
|
+
printf(" ║ %-49s║" . PHP_EOL, $builderTitle);
|
|
53
|
+
echo " ╚══════════════════════════════════════════════════╝" . PHP_EOL;
|
|
54
|
+
echo PHP_EOL;
|
|
55
|
+
echo " " . $this->translator->get('PROJECT_LABEL') . " : " . $projectName . PHP_EOL;
|
|
56
|
+
echo " " . $this->translator->get('LOCATION_LABEL') . " : " . $targetDir . PHP_EOL;
|
|
57
|
+
echo PHP_EOL;
|
|
58
|
+
|
|
59
|
+
// Toplam adım sayısını hesapla
|
|
60
|
+
$coreStubs = \ARTIFRAME_CLI_ROOT . '/core-stubs';
|
|
61
|
+
$stubCount = $this->countFiles($coreStubs);
|
|
62
|
+
$dynamicCount = 14; // createDynamicFiles içindeki dosya sayısı
|
|
63
|
+
$dirCount = 10; // oluşturulacak dinamik dizin sayısı
|
|
64
|
+
$this->totalSteps = $stubCount + $dynamicCount + $dirCount;
|
|
65
|
+
|
|
66
|
+
// ── FAZ 1: KOPYALAMA ─────────────────────────────────
|
|
67
|
+
$this->printPhaseHeader(1, 4, $this->translator->get('PHASE_COPYING'), $stubCount);
|
|
68
|
+
|
|
69
|
+
if (!is_dir($targetDir)) {
|
|
70
|
+
mkdir($targetDir, 0755, true);
|
|
71
|
+
}
|
|
72
|
+
$this->copyDirectory($coreStubs, $targetDir);
|
|
73
|
+
|
|
74
|
+
// 1.5 Dökümantasyon ve Readme Yerelleştirmesi
|
|
75
|
+
$lang = $this->translator->getLang();
|
|
76
|
+
$this->localizeDocsAndReadme($targetDir, $lang);
|
|
77
|
+
|
|
78
|
+
$this->finishPhase();
|
|
79
|
+
|
|
80
|
+
// ── FAZ 2: DİZİNLER ──────────────────────────────────
|
|
81
|
+
$this->printPhaseHeader(2, 4, $this->translator->get('PHASE_DIRS'), $dirCount);
|
|
82
|
+
|
|
83
|
+
$directoriesToCreate = [
|
|
84
|
+
'src/Auth',
|
|
85
|
+
'src/Email',
|
|
86
|
+
'src/Service',
|
|
87
|
+
'config',
|
|
88
|
+
'app',
|
|
89
|
+
'.agents',
|
|
90
|
+
'public/api/standart',
|
|
91
|
+
'public/api/switch-case',
|
|
92
|
+
'public/auth',
|
|
93
|
+
'public/assets/images',
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
foreach ($directoriesToCreate as $dir) {
|
|
97
|
+
$path = $targetDir . \DIRECTORY_SEPARATOR . $dir;
|
|
98
|
+
if (!is_dir($path)) {
|
|
99
|
+
mkdir($path, 0755, true);
|
|
100
|
+
}
|
|
101
|
+
$this->tick($dir . '/');
|
|
102
|
+
}
|
|
103
|
+
$this->finishPhase();
|
|
104
|
+
|
|
105
|
+
// ── FAZ 3: DİNAMİK DOSYALAR ──────────────────────────
|
|
106
|
+
$this->printPhaseHeader(3, 4, $this->translator->get('PHASE_FILES'), $dynamicCount);
|
|
107
|
+
$this->createDynamicFiles($targetDir);
|
|
108
|
+
$this->finishPhase();
|
|
109
|
+
|
|
110
|
+
// ── FAZ 4: COMPOSER ──────────────────────────────────
|
|
111
|
+
echo PHP_EOL;
|
|
112
|
+
$phaseLabel = 'Faz 4/4 — ' . $this->translator->get('PHASE_DEPS');
|
|
113
|
+
echo " ┌─────────────────────────────────────────────────┐" . PHP_EOL;
|
|
114
|
+
printf(" │ 📦 %-43s│" . PHP_EOL, $phaseLabel);
|
|
115
|
+
echo " └─────────────────────────────────────────────────┘" . PHP_EOL;
|
|
116
|
+
echo PHP_EOL;
|
|
117
|
+
|
|
118
|
+
$this->runComposerInstall($targetDir);
|
|
119
|
+
|
|
120
|
+
// ── BAŞARI ────────────────────────────────────────────
|
|
121
|
+
echo PHP_EOL;
|
|
122
|
+
$successTitle = $this->translator->get('SUCCESS_TITLE');
|
|
123
|
+
echo " ╔══════════════════════════════════════════════════╗" . PHP_EOL;
|
|
124
|
+
printf(" ║ %-49s║" . PHP_EOL, $successTitle);
|
|
125
|
+
echo " ╚══════════════════════════════════════════════════╝" . PHP_EOL;
|
|
126
|
+
echo PHP_EOL;
|
|
127
|
+
|
|
128
|
+
// Dizin ağacı
|
|
129
|
+
$this->printDirectoryTree($targetDir, $projectName);
|
|
130
|
+
|
|
131
|
+
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;
|
|
135
|
+
echo PHP_EOL;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── İlerleme Sistemi ─────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
private function printPhaseHeader(int $phase, int $total, string $label, int $fileCount): void
|
|
141
|
+
{
|
|
142
|
+
$filesLabel = $this->translator->get('FILES_TO_PROCESS', ['count' => $fileCount]);
|
|
143
|
+
echo PHP_EOL;
|
|
144
|
+
echo " ┌─────────────────────────────────────────────────┐" . PHP_EOL;
|
|
145
|
+
printf(" │ 📂 Faz %d/%d — %-36s│" . PHP_EOL, $phase, $total, $label);
|
|
146
|
+
printf(" │ %-43s│" . PHP_EOL, $filesLabel);
|
|
147
|
+
echo " └─────────────────────────────────────────────────┘" . PHP_EOL;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private function tick(string $label): void
|
|
151
|
+
{
|
|
152
|
+
$this->currentStep++;
|
|
153
|
+
$pct = (int) round(($this->currentStep / $this->totalSteps) * 100);
|
|
154
|
+
$filled = (int) round($pct / 5); // 20 karakterlik bar
|
|
155
|
+
$empty = max(0, 20 - $filled);
|
|
156
|
+
|
|
157
|
+
$bar = str_repeat('█', $filled) . str_repeat('░', $empty);
|
|
158
|
+
$line = sprintf(
|
|
159
|
+
" [%s] %3d%% (%d/%d) %-25s",
|
|
160
|
+
$bar,
|
|
161
|
+
$pct,
|
|
162
|
+
$this->currentStep,
|
|
163
|
+
$this->totalSteps,
|
|
164
|
+
mb_substr($label, 0, 25)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Aynı satırı güncelle (\r ile başa dön, yeni satır yok)
|
|
168
|
+
echo "\r" . $line;
|
|
169
|
+
flush();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private function finishPhase(): void
|
|
173
|
+
{
|
|
174
|
+
echo PHP_EOL; // İlerleme satırını tamamla
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Dosya Sayacı ─────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
private function countFiles(string $dir): int
|
|
180
|
+
{
|
|
181
|
+
$count = 0;
|
|
182
|
+
$items = new \RecursiveIteratorIterator(
|
|
183
|
+
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
|
|
184
|
+
);
|
|
185
|
+
foreach ($items as $item) {
|
|
186
|
+
if ($item->isFile()) {
|
|
187
|
+
$count++;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return $count;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── Dizin Ağacı Çizici ───────────────────────────────────
|
|
194
|
+
|
|
195
|
+
private function printDirectoryTree(string $dir, string $projectName): void
|
|
196
|
+
{
|
|
197
|
+
echo " 📂 " . $projectName . "/" . PHP_EOL;
|
|
198
|
+
|
|
199
|
+
$priorityOrder = [
|
|
200
|
+
'bin', 'config', 'src', 'app', 'public', '.agents',
|
|
201
|
+
'vendor', '.env', '.env.example', '.gitignore',
|
|
202
|
+
'composer.json', 'composer.lock', 'schema.sql',
|
|
203
|
+
'README.md', 'OKUBENI.md', 'LESEMICH.md', 'LISEZMOI.md', 'LEAME.md',
|
|
204
|
+
'kilavuz.html', 'guide.html', 'handbuch.html', 'guide_fr.html', 'guia.html', 'LICENSE',
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
$entries = array_filter(scandir($dir), fn($e) => $e !== '.' && $e !== '..');
|
|
208
|
+
|
|
209
|
+
usort($entries, function ($a, $b) use ($priorityOrder) {
|
|
210
|
+
$ia = array_search($a, $priorityOrder);
|
|
211
|
+
$ib = array_search($b, $priorityOrder);
|
|
212
|
+
$ia = ($ia === false) ? 99 : $ia;
|
|
213
|
+
$ib = ($ib === false) ? 99 : $ib;
|
|
214
|
+
return $ia <=> $ib;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
$entries = array_values($entries);
|
|
218
|
+
$total = count($entries);
|
|
219
|
+
|
|
220
|
+
foreach ($entries as $i => $entry) {
|
|
221
|
+
$isLast = ($i === $total - 1);
|
|
222
|
+
$connector = $isLast ? '└──' : '├──';
|
|
223
|
+
$fullPath = $dir . \DIRECTORY_SEPARATOR . $entry;
|
|
224
|
+
|
|
225
|
+
if (is_dir($fullPath)) {
|
|
226
|
+
$subItems = array_filter(scandir($fullPath), fn($e) => $e !== '.' && $e !== '..');
|
|
227
|
+
$subCount = count($subItems);
|
|
228
|
+
$countStr = $subCount > 0 ? ' ' . $this->translator->get('DIR_ITEM_COUNT', ['count' => $subCount]) : '';
|
|
229
|
+
echo " │ {$connector} 📂 {$entry}/{$countStr}" . PHP_EOL;
|
|
230
|
+
} else {
|
|
231
|
+
$size = filesize($fullPath);
|
|
232
|
+
$sizeStr = $size > 1024 ? round($size / 1024, 1) . ' KB' : $size . ' B';
|
|
233
|
+
echo " │ {$connector} 📄 {$entry} [{$sizeStr}]" . PHP_EOL;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
echo " │" . PHP_EOL;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─── Yol Çözümleyici ──────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
private function resolvePath(string $path): string
|
|
242
|
+
{
|
|
243
|
+
if (strpos($path, '/') === 0 || preg_match('/^[a-zA-Z]:\\\\/', $path)) {
|
|
244
|
+
return str_replace('\\', '/', $path);
|
|
245
|
+
}
|
|
246
|
+
return str_replace('\\', '/', getcwd() . '/' . $path);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private function isComposerInstalled(): bool
|
|
250
|
+
{
|
|
251
|
+
$output = [];
|
|
252
|
+
$returnVar = -1;
|
|
253
|
+
exec("composer --version 2>&1", $output, $returnVar);
|
|
254
|
+
return $returnVar === 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── Kopyalama Motoru ─────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
private function copyDirectory(string $source, string $target): void
|
|
260
|
+
{
|
|
261
|
+
if (!is_dir($target)) {
|
|
262
|
+
mkdir($target, 0755, true);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
$dir = opendir($source);
|
|
266
|
+
while (false !== ($file = readdir($dir))) {
|
|
267
|
+
if ($file === '.' || $file === '..') {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
$srcFile = $source . '/' . $file;
|
|
272
|
+
$targetFile = $target . '/' . $file;
|
|
273
|
+
|
|
274
|
+
if (is_dir($srcFile)) {
|
|
275
|
+
$this->copyDirectory($srcFile, $targetFile);
|
|
276
|
+
} else {
|
|
277
|
+
copy($srcFile, $targetFile);
|
|
278
|
+
$this->tick($file);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
closedir($dir);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private function localizeDocsAndReadme(string $targetDir, string $lang): void
|
|
285
|
+
{
|
|
286
|
+
$langMap = [
|
|
287
|
+
'tr' => ['docs' => 'kilavuz.html', 'readme' => 'OKUBENI.md'],
|
|
288
|
+
'en' => ['docs' => 'guide.html', 'readme' => 'README.md'],
|
|
289
|
+
'de' => ['docs' => 'handbuch.html', 'readme' => 'LESEMICH.md'],
|
|
290
|
+
'fr' => ['docs' => 'guide_fr.html', 'readme' => 'LISEZMOI.md'],
|
|
291
|
+
'es' => ['docs' => 'guia.html', 'readme' => 'LEAME.md']
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
$docNames = $langMap[$lang] ?? $langMap['en'];
|
|
295
|
+
|
|
296
|
+
if (file_exists($targetDir . '/docs/' . $lang . '.html')) {
|
|
297
|
+
copy($targetDir . '/docs/' . $lang . '.html', $targetDir . '/' . $docNames['docs']);
|
|
298
|
+
}
|
|
299
|
+
if (file_exists($targetDir . '/readme/' . $lang . '.md')) {
|
|
300
|
+
copy($targetDir . '/readme/' . $lang . '.md', $targetDir . '/' . $docNames['readme']);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
$this->removeDirectory($targetDir . '/docs');
|
|
304
|
+
$this->removeDirectory($targetDir . '/readme');
|
|
305
|
+
if (file_exists($targetDir . '/README.md') && $lang !== 'en') {
|
|
306
|
+
unlink($targetDir . '/README.md'); // Remove default README if copied directly from core-stubs root
|
|
307
|
+
}
|
|
308
|
+
if (file_exists($targetDir . '/kilavuz.html') && $lang !== 'tr') {
|
|
309
|
+
unlink($targetDir . '/kilavuz.html'); // Remove default kilavuz.html if copied directly from core-stubs root
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private function removeDirectory(string $dir): void
|
|
314
|
+
{
|
|
315
|
+
if (!is_dir($dir)) return;
|
|
316
|
+
$items = new \RecursiveIteratorIterator(
|
|
317
|
+
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
|
|
318
|
+
\RecursiveIteratorIterator::CHILD_FIRST
|
|
319
|
+
);
|
|
320
|
+
foreach ($items as $item) {
|
|
321
|
+
$item->isDir() ? rmdir($item->getRealPath()) : unlink($item->getRealPath());
|
|
322
|
+
}
|
|
323
|
+
rmdir($dir);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ─── Dinamik Dosya Üretici ────────────────────────────────
|
|
327
|
+
|
|
328
|
+
private function createDynamicFiles(string $targetDir): void
|
|
329
|
+
{
|
|
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";
|
|
331
|
+
|
|
332
|
+
// composer.json
|
|
333
|
+
$composerJson = <<<JSON
|
|
334
|
+
{
|
|
335
|
+
"name": "artiframe/project",
|
|
336
|
+
"description": "ArtiFrame Core PHP Application",
|
|
337
|
+
"type": "project",
|
|
338
|
+
"license": "AGPL-3.0-or-later",
|
|
339
|
+
"require": {
|
|
340
|
+
"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"
|
|
345
|
+
},
|
|
346
|
+
"autoload": {
|
|
347
|
+
"psr-4": {
|
|
348
|
+
"Bin\\\\": "bin/",
|
|
349
|
+
"App\\\\": "app/",
|
|
350
|
+
"Src\\\\": "src/"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
"authors": [
|
|
354
|
+
{
|
|
355
|
+
"name": "Artilingo",
|
|
356
|
+
"homepage": "https://artilingo.com"
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
"config": {
|
|
360
|
+
"optimize-autoloader": true,
|
|
361
|
+
"preferred-install": "dist",
|
|
362
|
+
"sort-packages": true
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
JSON;
|
|
366
|
+
file_put_contents($targetDir . '/composer.json', $composerJson);
|
|
367
|
+
$this->tick('composer.json');
|
|
368
|
+
|
|
369
|
+
// config/app-version.php
|
|
370
|
+
file_put_contents(
|
|
371
|
+
$targetDir . '/config/app-version.php',
|
|
372
|
+
$licenseHeader . "\n// APP_ENV: 0 = Canlı (Prod), 1 = Geliştirici (Debug)\ndefine('APP_ENV', 1);\ndefine('APP_VERSION', '1.0.0');\n"
|
|
373
|
+
);
|
|
374
|
+
$this->tick('config/app-version.php');
|
|
375
|
+
|
|
376
|
+
// config/central-control.php
|
|
377
|
+
file_put_contents($targetDir . '/config/central-control.php', $licenseHeader . "\n// Central Control Configurations\n");
|
|
378
|
+
$this->tick('config/central-control.php');
|
|
379
|
+
|
|
380
|
+
// config/api-security.php
|
|
381
|
+
file_put_contents($targetDir . '/config/api-security.php', $licenseHeader . "\n// API Security Rules\n");
|
|
382
|
+
$this->tick('config/api-security.php');
|
|
383
|
+
|
|
384
|
+
// .env & .env.example
|
|
385
|
+
$envStub = implode("\n", [
|
|
386
|
+
"# ================================================",
|
|
387
|
+
"# ArtiFrame Environment Configuration",
|
|
388
|
+
"# Hassas bilgileri buraya girin. Bu dosyayı asla",
|
|
389
|
+
"# git reposuna eklemeyin! Bkz: .gitignore",
|
|
390
|
+
"# ================================================",
|
|
391
|
+
"",
|
|
392
|
+
"# Uygulama",
|
|
393
|
+
"APP_NAME=ArtiFrame",
|
|
394
|
+
"APP_URL=http://localhost",
|
|
395
|
+
"",
|
|
396
|
+
"# Veritabanı (MySQL / MariaDB)",
|
|
397
|
+
"DB_HOST=localhost",
|
|
398
|
+
"DB_USER=root",
|
|
399
|
+
"DB_PASS=",
|
|
400
|
+
"DB_NAME=artiframe",
|
|
401
|
+
"",
|
|
402
|
+
"# SMTP (PHPMailer)",
|
|
403
|
+
"MAIL_HOST=smtp.example.com",
|
|
404
|
+
"MAIL_PORT=587",
|
|
405
|
+
"MAIL_USERNAME=",
|
|
406
|
+
"MAIL_PASSWORD=",
|
|
407
|
+
"MAIL_FROM_ADDRESS=noreply@example.com",
|
|
408
|
+
"MAIL_FROM_NAME=ArtiFrame",
|
|
409
|
+
"MAIL_ENCRYPTION=tls",
|
|
410
|
+
"",
|
|
411
|
+
"# Cloudflare R2 (AWS S3 Uyumlu Depolama)",
|
|
412
|
+
"R2_ACCOUNT_ID=",
|
|
413
|
+
"R2_ACCESS_KEY=",
|
|
414
|
+
"R2_SECRET_KEY=",
|
|
415
|
+
"R2_BUCKET_NAME=",
|
|
416
|
+
"R2_PUBLIC_URL=",
|
|
417
|
+
"",
|
|
418
|
+
"# Redis",
|
|
419
|
+
"REDIS_HOST=127.0.0.1",
|
|
420
|
+
"REDIS_PORT=6379",
|
|
421
|
+
"REDIS_PASSWORD=",
|
|
422
|
+
"REDIS_DB=0",
|
|
423
|
+
]) . "\n";
|
|
424
|
+
file_put_contents($targetDir . '/.env', $envStub);
|
|
425
|
+
$this->tick('.env');
|
|
426
|
+
file_put_contents($targetDir . '/.env.example', $envStub);
|
|
427
|
+
$this->tick('.env.example');
|
|
428
|
+
|
|
429
|
+
// schema.sql
|
|
430
|
+
file_put_contents($targetDir . '/schema.sql', "-- ArtiFrame Initial DB Schema\n-- Created: " . date('Y-m-d') . "\n\n");
|
|
431
|
+
$this->tick('schema.sql');
|
|
432
|
+
|
|
433
|
+
// src/Email/EmailService.php
|
|
434
|
+
$emailServiceContent = <<<'EMAILPHP'
|
|
435
|
+
<?php
|
|
436
|
+
/**
|
|
437
|
+
* ArtiFrame Core Engine
|
|
438
|
+
*
|
|
439
|
+
* @package ArtiFrame
|
|
440
|
+
* @author Artilingo
|
|
441
|
+
* @license AGPLv3 (Attribution-ShareAlike Required)
|
|
442
|
+
* @link https://artiframe.org
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
namespace Src\Email;
|
|
446
|
+
|
|
447
|
+
use PHPMailer\PHPMailer\PHPMailer;
|
|
448
|
+
use PHPMailer\PHPMailer\SMTP;
|
|
449
|
+
use PHPMailer\PHPMailer\Exception;
|
|
450
|
+
|
|
451
|
+
class EmailService
|
|
452
|
+
{
|
|
453
|
+
private PHPMailer $mailer;
|
|
454
|
+
|
|
455
|
+
public function __construct()
|
|
456
|
+
{
|
|
457
|
+
$this->mailer = new PHPMailer(true);
|
|
458
|
+
$this->mailer->isSMTP();
|
|
459
|
+
$this->mailer->Host = $_ENV['MAIL_HOST'] ?? 'smtp.example.com';
|
|
460
|
+
$this->mailer->SMTPAuth = true;
|
|
461
|
+
$this->mailer->Username = $_ENV['MAIL_USERNAME'] ?? '';
|
|
462
|
+
$this->mailer->Password = $_ENV['MAIL_PASSWORD'] ?? '';
|
|
463
|
+
$this->mailer->SMTPSecure = $_ENV['MAIL_ENCRYPTION'] ?? PHPMailer::ENCRYPTION_STARTTLS;
|
|
464
|
+
$this->mailer->Port = (int) ($_ENV['MAIL_PORT'] ?? 587);
|
|
465
|
+
$this->mailer->CharSet = 'UTF-8';
|
|
466
|
+
$this->mailer->setFrom(
|
|
467
|
+
$_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@example.com',
|
|
468
|
+
$_ENV['MAIL_FROM_NAME'] ?? 'ArtiFrame'
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Tek alıcıya HTML e-posta gönderir.
|
|
474
|
+
*/
|
|
475
|
+
public function send(string $toEmail, string $toName, string $subject, string $body): bool
|
|
476
|
+
{
|
|
477
|
+
try {
|
|
478
|
+
$this->mailer->clearAddresses();
|
|
479
|
+
$this->mailer->addAddress($toEmail, $toName);
|
|
480
|
+
$this->mailer->isHTML(true);
|
|
481
|
+
$this->mailer->Subject = $subject;
|
|
482
|
+
$this->mailer->Body = $body;
|
|
483
|
+
$this->mailer->send();
|
|
484
|
+
return true;
|
|
485
|
+
} catch (Exception $e) {
|
|
486
|
+
$isDev = (defined('APP_ENV') && APP_ENV == 1);
|
|
487
|
+
error_log('EmailService Hatası: ' . $e->getMessage());
|
|
488
|
+
if ($isDev) {
|
|
489
|
+
error_log('PHPMailer Detay: ' . $this->mailer->ErrorInfo);
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
EMAILPHP;
|
|
496
|
+
file_put_contents($targetDir . '/src/Email/EmailService.php', $emailServiceContent);
|
|
497
|
+
$this->tick('EmailService.php');
|
|
498
|
+
|
|
499
|
+
// src/Service/RedisService.php
|
|
500
|
+
$redisServiceContent = <<<'REDISPHP'
|
|
501
|
+
<?php
|
|
502
|
+
/**
|
|
503
|
+
* ArtiFrame Core Engine
|
|
504
|
+
*
|
|
505
|
+
* @package ArtiFrame
|
|
506
|
+
* @author Artilingo
|
|
507
|
+
* @license AGPLv3 (Attribution-ShareAlike Required)
|
|
508
|
+
* @link https://artiframe.org
|
|
509
|
+
*/
|
|
510
|
+
|
|
511
|
+
namespace Src\Service;
|
|
512
|
+
|
|
513
|
+
use Predis\Client as RedisClient;
|
|
514
|
+
|
|
515
|
+
class RedisService
|
|
516
|
+
{
|
|
517
|
+
private static ?RedisClient $instance = null;
|
|
518
|
+
|
|
519
|
+
public static function getInstance(): RedisClient
|
|
520
|
+
{
|
|
521
|
+
if (self::$instance === null) {
|
|
522
|
+
self::$instance = new RedisClient([
|
|
523
|
+
'scheme' => 'tcp',
|
|
524
|
+
'host' => $_ENV['REDIS_HOST'] ?? '127.0.0.1',
|
|
525
|
+
'port' => (int) ($_ENV['REDIS_PORT'] ?? 6379),
|
|
526
|
+
'password' => $_ENV['REDIS_PASSWORD'] ?: null,
|
|
527
|
+
'database' => (int) ($_ENV['REDIS_DB'] ?? 0),
|
|
528
|
+
]);
|
|
529
|
+
}
|
|
530
|
+
return self::$instance;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Değer yazar. Opsiyonel TTL (saniye cinsinden). */
|
|
534
|
+
public static function set(string $key, mixed $value, int $ttl = 0): void
|
|
535
|
+
{
|
|
536
|
+
$client = self::getInstance();
|
|
537
|
+
$encoded = is_array($value) ? json_encode($value) : $value;
|
|
538
|
+
$ttl > 0 ? $client->setex($key, $ttl, $encoded) : $client->set($key, $encoded);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Değer okur. JSON ise otomatik çözümler, yoksa null döner. */
|
|
542
|
+
public static function get(string $key): mixed
|
|
543
|
+
{
|
|
544
|
+
$value = self::getInstance()->get($key);
|
|
545
|
+
if ($value === null) return null;
|
|
546
|
+
$decoded = json_decode($value, true);
|
|
547
|
+
return (json_last_error() === JSON_ERROR_NONE) ? $decoded : $value;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Anahtarı siler. */
|
|
551
|
+
public static function delete(string $key): void
|
|
552
|
+
{
|
|
553
|
+
self::getInstance()->del($key);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Anahtarın var olup olmadığını kontrol eder. */
|
|
557
|
+
public static function exists(string $key): bool
|
|
558
|
+
{
|
|
559
|
+
return (bool) self::getInstance()->exists($key);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
REDISPHP;
|
|
563
|
+
file_put_contents($targetDir . '/src/Service/RedisService.php', $redisServiceContent);
|
|
564
|
+
$this->tick('RedisService.php');
|
|
565
|
+
file_put_contents($targetDir . '/LICENSE', "GNU AFFERO GENERAL PUBLIC LICENSE Version 3\n");
|
|
566
|
+
$this->tick('LICENSE');
|
|
567
|
+
|
|
568
|
+
// public/index.php — localized welcome page
|
|
569
|
+
$lang = $this->translator->getLang();
|
|
570
|
+
$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'],
|
|
576
|
+
][$lang] ?? ['file' => 'guide.html', 'installed' => 'ArtiFrame Installed Successfully!', 'guide' => 'To get started, see'];
|
|
577
|
+
|
|
578
|
+
$indexContent = "<?php\nrequire_once __DIR__ . '/../app/ViewControl.php';\n?>\n"
|
|
579
|
+
. "<!DOCTYPE html>\n"
|
|
580
|
+
. "<html lang=\"{$lang}\" data-theme=\"default\" data-mode=\"light\">\n"
|
|
581
|
+
. "<head>\n"
|
|
582
|
+
. " <?php require_once __DIR__ . '/includes/head.php'; ?>\n"
|
|
583
|
+
. " <title>ArtiFrame | Welcome</title>\n"
|
|
584
|
+
. "</head>\n"
|
|
585
|
+
. "<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"
|
|
592
|
+
. "</body>\n"
|
|
593
|
+
. "</html>\n";
|
|
594
|
+
file_put_contents($targetDir . '/public/index.php', $indexContent);
|
|
595
|
+
$this->tick('public/index.php');
|
|
596
|
+
|
|
597
|
+
// public/.htaccess
|
|
598
|
+
$htaccessContent = <<<HTACCESS
|
|
599
|
+
RewriteEngine On
|
|
600
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
|
601
|
+
RewriteCond %{REQUEST_FILENAME} !-d
|
|
602
|
+
RewriteRule ^(.*)$ index.php [QSA,L]
|
|
603
|
+
HTACCESS;
|
|
604
|
+
file_put_contents($targetDir . '/public/.htaccess', $htaccessContent);
|
|
605
|
+
$this->tick('public/.htaccess');
|
|
606
|
+
|
|
607
|
+
// public/site.webmanifest
|
|
608
|
+
$manifestContent = <<<MANIFEST
|
|
609
|
+
{
|
|
610
|
+
"name": "ArtiFrame App",
|
|
611
|
+
"short_name": "ArtiFrame",
|
|
612
|
+
"start_url": "/",
|
|
613
|
+
"display": "standalone",
|
|
614
|
+
"background_color": "#ffffff",
|
|
615
|
+
"theme_color": "#3b82f6"
|
|
616
|
+
}
|
|
617
|
+
MANIFEST;
|
|
618
|
+
file_put_contents($targetDir . '/public/site.webmanifest', $manifestContent);
|
|
619
|
+
$this->tick('public/site.webmanifest');
|
|
620
|
+
|
|
621
|
+
// .agents/AGENTS.md
|
|
622
|
+
file_put_contents($targetDir . '/.agents/AGENTS.md', "# AI Agent Context Rules\n\nArtiFrame proje standartları...");
|
|
623
|
+
$this->tick('.agents/AGENTS.md');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ─── Composer Install ─────────────────────────────────────
|
|
627
|
+
|
|
628
|
+
private function runComposerInstall(string $targetDir): void
|
|
629
|
+
{
|
|
630
|
+
$command = "cd " . escapeshellarg($targetDir) . " && composer install";
|
|
631
|
+
passthru($command);
|
|
632
|
+
}
|
|
633
|
+
}
|