create-prisma-php-app 4.0.0-rc → 4.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/dist/bootstrap.php +231 -93
- package/dist/index.js +1 -1
- package/dist/public/js/pp-reactive-v1.js +1 -1
- package/dist/src/app/layout.php +1 -1
- package/package.json +1 -1
package/dist/bootstrap.php
CHANGED
|
@@ -103,10 +103,14 @@ final class Bootstrap extends RuntimeException
|
|
|
103
103
|
self::authenticateUserToken();
|
|
104
104
|
|
|
105
105
|
self::$requestFilePath = APP_PATH . Request::$pathname;
|
|
106
|
-
self::$parentLayoutPath = APP_PATH . '/layout.php';
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
if (!empty(self::$layoutsToInclude)) {
|
|
108
|
+
self::$parentLayoutPath = self::$layoutsToInclude[0];
|
|
109
|
+
self::$isParentLayout = true;
|
|
110
|
+
} else {
|
|
111
|
+
self::$parentLayoutPath = APP_PATH . '/layout.php';
|
|
112
|
+
self::$isParentLayout = false;
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
self::$isContentVariableIncluded = self::containsChildren(self::$parentLayoutPath);
|
|
112
116
|
if (!self::$isContentVariableIncluded) {
|
|
@@ -259,60 +263,191 @@ final class Bootstrap extends RuntimeException
|
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
$
|
|
263
|
-
|
|
264
|
-
$
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
266
|
+
$layoutsToInclude = self::collectLayouts($pathname, $groupFolder, $dynamicRoute ?? null);
|
|
267
|
+
} else {
|
|
268
|
+
$includePath = $baseDir . self::getFilePrecedence();
|
|
269
|
+
$layoutsToInclude = self::collectRootLayouts();
|
|
270
|
+
}
|
|
268
271
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
return [
|
|
273
|
+
'path' => $includePath,
|
|
274
|
+
'layouts' => $layoutsToInclude,
|
|
275
|
+
'pathname' => $pathname,
|
|
276
|
+
'uri' => $requestUri
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private static function collectLayouts(string $pathname, ?string $groupFolder, ?string $dynamicRoute): array
|
|
281
|
+
{
|
|
282
|
+
$layoutsToInclude = [];
|
|
283
|
+
$baseDir = APP_PATH;
|
|
284
|
+
|
|
285
|
+
$rootLayout = $baseDir . '/layout.php';
|
|
286
|
+
if (self::fileExistsCached($rootLayout)) {
|
|
287
|
+
$layoutsToInclude[] = $rootLayout;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
$groupName = null;
|
|
291
|
+
$groupParentPath = '';
|
|
292
|
+
$pathAfterGroup = '';
|
|
293
|
+
|
|
294
|
+
if ($groupFolder) {
|
|
295
|
+
$normalizedGroupFolder = str_replace('\\', '/', $groupFolder);
|
|
296
|
+
|
|
297
|
+
if (preg_match('#^\.?/src/app/(.+)/\(([^)]+)\)/(.+)$#', $normalizedGroupFolder, $matches)) {
|
|
298
|
+
$groupParentPath = $matches[1];
|
|
299
|
+
$groupName = $matches[2];
|
|
300
|
+
$pathAfterGroup = dirname($matches[3]);
|
|
301
|
+
if ($pathAfterGroup === '.') {
|
|
302
|
+
$pathAfterGroup = '';
|
|
272
303
|
}
|
|
304
|
+
} elseif (preg_match('#^\.?/src/app/\(([^)]+)\)/(.+)$#', $normalizedGroupFolder, $matches)) {
|
|
305
|
+
$groupName = $matches[1];
|
|
306
|
+
$pathAfterGroup = dirname($matches[2]);
|
|
307
|
+
if ($pathAfterGroup === '.') {
|
|
308
|
+
$pathAfterGroup = '';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if ($groupName && $groupParentPath) {
|
|
314
|
+
$currentPath = $baseDir;
|
|
315
|
+
foreach (explode('/', $groupParentPath) as $segment) {
|
|
316
|
+
if (empty($segment)) continue;
|
|
273
317
|
|
|
274
318
|
$currentPath .= '/' . $segment;
|
|
275
319
|
$potentialLayoutPath = $currentPath . '/layout.php';
|
|
320
|
+
|
|
276
321
|
if (self::fileExistsCached($potentialLayoutPath) && !in_array($potentialLayoutPath, $layoutsToInclude, true)) {
|
|
277
322
|
$layoutsToInclude[] = $potentialLayoutPath;
|
|
278
323
|
}
|
|
279
324
|
}
|
|
280
325
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
326
|
+
$groupLayoutPath = $baseDir . '/' . $groupParentPath . "/($groupName)/layout.php";
|
|
327
|
+
if (self::fileExistsCached($groupLayoutPath)) {
|
|
328
|
+
$layoutsToInclude[] = $groupLayoutPath;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!empty($pathAfterGroup)) {
|
|
332
|
+
$currentPath = $baseDir . '/' . $groupParentPath . "/($groupName)";
|
|
333
|
+
foreach (explode('/', $pathAfterGroup) as $segment) {
|
|
334
|
+
if (empty($segment)) continue;
|
|
335
|
+
|
|
336
|
+
$currentPath .= '/' . $segment;
|
|
337
|
+
$potentialLayoutPath = $currentPath . '/layout.php';
|
|
338
|
+
|
|
339
|
+
if (self::fileExistsCached($potentialLayoutPath) && !in_array($potentialLayoutPath, $layoutsToInclude, true)) {
|
|
340
|
+
$layoutsToInclude[] = $potentialLayoutPath;
|
|
289
341
|
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} elseif ($groupName && !$groupParentPath) {
|
|
345
|
+
$groupLayoutPath = $baseDir . "/($groupName)/layout.php";
|
|
346
|
+
if (self::fileExistsCached($groupLayoutPath)) {
|
|
347
|
+
$layoutsToInclude[] = $groupLayoutPath;
|
|
348
|
+
}
|
|
290
349
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
350
|
+
if (!empty($pathAfterGroup)) {
|
|
351
|
+
$currentPath = $baseDir . "/($groupName)";
|
|
352
|
+
foreach (explode('/', $pathAfterGroup) as $segment) {
|
|
353
|
+
if (empty($segment)) continue;
|
|
354
|
+
|
|
355
|
+
$currentPath .= '/' . $segment;
|
|
356
|
+
$potentialLayoutPath = $currentPath . '/layout.php';
|
|
357
|
+
|
|
358
|
+
if (self::fileExistsCached($potentialLayoutPath) && !in_array($potentialLayoutPath, $layoutsToInclude, true)) {
|
|
359
|
+
$layoutsToInclude[] = $potentialLayoutPath;
|
|
295
360
|
}
|
|
296
361
|
}
|
|
297
362
|
}
|
|
363
|
+
} else {
|
|
364
|
+
$currentPath = $baseDir;
|
|
365
|
+
foreach (explode('/', $pathname) as $segment) {
|
|
366
|
+
if (empty($segment)) continue;
|
|
298
367
|
|
|
299
|
-
|
|
300
|
-
$
|
|
368
|
+
$currentPath .= '/' . $segment;
|
|
369
|
+
$potentialLayoutPath = $currentPath . '/layout.php';
|
|
370
|
+
|
|
371
|
+
if ($potentialLayoutPath === $rootLayout) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (self::fileExistsCached($potentialLayoutPath) && !in_array($potentialLayoutPath, $layoutsToInclude, true)) {
|
|
376
|
+
$layoutsToInclude[] = $potentialLayoutPath;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (isset($dynamicRoute) && !empty($dynamicRoute)) {
|
|
382
|
+
$currentDynamicPath = $baseDir;
|
|
383
|
+
foreach (explode('/', $dynamicRoute) as $segment) {
|
|
384
|
+
if (empty($segment) || $segment === 'src' || $segment === 'app') {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
$currentDynamicPath .= '/' . $segment;
|
|
389
|
+
$potentialDynamicRoute = $currentDynamicPath . '/layout.php';
|
|
390
|
+
if (self::fileExistsCached($potentialDynamicRoute) && !in_array($potentialDynamicRoute, $layoutsToInclude, true)) {
|
|
391
|
+
$layoutsToInclude[] = $potentialDynamicRoute;
|
|
392
|
+
}
|
|
301
393
|
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (empty($layoutsToInclude)) {
|
|
397
|
+
$layoutsToInclude = self::findFirstGroupLayout();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return $layoutsToInclude;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private static function collectRootLayouts(): array
|
|
404
|
+
{
|
|
405
|
+
$layoutsToInclude = [];
|
|
406
|
+
$baseDir = APP_PATH;
|
|
407
|
+
$rootLayout = $baseDir . '/layout.php';
|
|
408
|
+
|
|
409
|
+
if (self::fileExistsCached($rootLayout)) {
|
|
410
|
+
$layoutsToInclude[] = $rootLayout;
|
|
302
411
|
} else {
|
|
303
|
-
$
|
|
412
|
+
$layoutsToInclude = self::findFirstGroupLayout();
|
|
413
|
+
|
|
414
|
+
if (empty($layoutsToInclude)) {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
304
417
|
}
|
|
305
418
|
|
|
306
|
-
return
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
419
|
+
return $layoutsToInclude;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private static function findFirstGroupLayout(): array
|
|
423
|
+
{
|
|
424
|
+
$baseDir = APP_PATH;
|
|
425
|
+
$layoutsToInclude = [];
|
|
426
|
+
|
|
427
|
+
if (is_dir($baseDir)) {
|
|
428
|
+
$items = scandir($baseDir);
|
|
429
|
+
foreach ($items as $item) {
|
|
430
|
+
if ($item === '.' || $item === '..') {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (preg_match('/^\([^)]+\)$/', $item)) {
|
|
435
|
+
$groupLayoutPath = $baseDir . '/' . $item . '/layout.php';
|
|
436
|
+
if (self::fileExistsCached($groupLayoutPath)) {
|
|
437
|
+
$layoutsToInclude[] = $groupLayoutPath;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return $layoutsToInclude;
|
|
312
445
|
}
|
|
313
446
|
|
|
314
447
|
private static function getFilePrecedence(): ?string
|
|
315
448
|
{
|
|
449
|
+
$baseDir = APP_PATH;
|
|
450
|
+
|
|
316
451
|
foreach (PrismaPHPSettings::$routeFiles as $route) {
|
|
317
452
|
if (pathinfo($route, PATHINFO_EXTENSION) !== 'php') {
|
|
318
453
|
continue;
|
|
@@ -324,6 +459,27 @@ final class Bootstrap extends RuntimeException
|
|
|
324
459
|
return '/index.php';
|
|
325
460
|
}
|
|
326
461
|
}
|
|
462
|
+
|
|
463
|
+
if (is_dir($baseDir)) {
|
|
464
|
+
$items = scandir($baseDir);
|
|
465
|
+
foreach ($items as $item) {
|
|
466
|
+
if ($item === '.' || $item === '..') {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (preg_match('/^\([^)]+\)$/', $item)) {
|
|
471
|
+
$groupDir = $baseDir . '/' . $item;
|
|
472
|
+
|
|
473
|
+
if (file_exists($groupDir . '/route.php')) {
|
|
474
|
+
return '/' . $item . '/route.php';
|
|
475
|
+
}
|
|
476
|
+
if (file_exists($groupDir . '/index.php')) {
|
|
477
|
+
return '/' . $item . '/index.php';
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
327
483
|
return null;
|
|
328
484
|
}
|
|
329
485
|
|
|
@@ -473,6 +629,7 @@ final class Bootstrap extends RuntimeException
|
|
|
473
629
|
if (pathinfo($route, PATHINFO_EXTENSION) !== 'php') {
|
|
474
630
|
continue;
|
|
475
631
|
}
|
|
632
|
+
|
|
476
633
|
$normalizedRoute = trim(str_replace('\\', '/', $route), '.');
|
|
477
634
|
$cleanedRoute = preg_replace('/\/\([^)]+\)/', '', $normalizedRoute);
|
|
478
635
|
|
|
@@ -484,22 +641,26 @@ final class Bootstrap extends RuntimeException
|
|
|
484
641
|
}
|
|
485
642
|
}
|
|
486
643
|
|
|
487
|
-
|
|
488
|
-
|
|
644
|
+
if (!$bestMatch) {
|
|
645
|
+
foreach (PrismaPHPSettings::$routeFiles as $route) {
|
|
646
|
+
if (pathinfo($route, PATHINFO_EXTENSION) !== 'php') {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
489
649
|
|
|
490
|
-
|
|
491
|
-
{
|
|
492
|
-
$lastSlashPos = strrpos($pathname, '/');
|
|
493
|
-
if ($lastSlashPos === false) {
|
|
494
|
-
return "";
|
|
495
|
-
}
|
|
650
|
+
$normalizedRoute = trim(str_replace('\\', '/', $route), '.');
|
|
496
651
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
652
|
+
if (preg_match('/\/\(([^)]+)\)\//', $normalizedRoute, $matches)) {
|
|
653
|
+
$cleanedRoute = preg_replace('/\/\([^)]+\)/', '', $normalizedRoute);
|
|
654
|
+
|
|
655
|
+
if ($cleanedRoute === $routeFile || $cleanedRoute === $indexFile) {
|
|
656
|
+
$bestMatch = $normalizedRoute;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
500
661
|
}
|
|
501
662
|
|
|
502
|
-
return
|
|
663
|
+
return $bestMatch;
|
|
503
664
|
}
|
|
504
665
|
|
|
505
666
|
private static function singleDynamicRoute($pathnameSegments, $routeSegments)
|
|
@@ -568,22 +729,7 @@ final class Bootstrap extends RuntimeException
|
|
|
568
729
|
}
|
|
569
730
|
}
|
|
570
731
|
|
|
571
|
-
public static function
|
|
572
|
-
{
|
|
573
|
-
if (!self::fileExistsCached($filePath)) {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
$fileContent = @file_get_contents($filePath);
|
|
578
|
-
if ($fileContent === false) {
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
$pattern = '/\<\?=\s*MainLayout::\$childLayoutChildren\s*;?\s*\?>|echo\s*MainLayout::\$childLayoutChildren\s*;?/';
|
|
583
|
-
return (bool) preg_match($pattern, $fileContent);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
private static function containsChildren($filePath): bool
|
|
732
|
+
public static function containsChildren($filePath): bool
|
|
587
733
|
{
|
|
588
734
|
if (!self::fileExistsCached($filePath)) {
|
|
589
735
|
return false;
|
|
@@ -997,40 +1143,32 @@ try {
|
|
|
997
1143
|
}
|
|
998
1144
|
|
|
999
1145
|
if (!empty(Bootstrap::$contentToInclude) && !empty(Request::$fileToInclude)) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
MainLayout::$childLayoutChildren = ob_get_clean();
|
|
1004
|
-
}
|
|
1146
|
+
ob_start();
|
|
1147
|
+
require_once Bootstrap::$contentToInclude;
|
|
1148
|
+
MainLayout::$children = ob_get_clean();
|
|
1005
1149
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1150
|
+
if (count(Bootstrap::$layoutsToInclude) > 1) {
|
|
1151
|
+
$nestedLayouts = array_slice(Bootstrap::$layoutsToInclude, 1);
|
|
1010
1152
|
|
|
1011
|
-
|
|
1012
|
-
Bootstrap
|
|
1013
|
-
|
|
1153
|
+
foreach (array_reverse($nestedLayouts) as $layoutPath) {
|
|
1154
|
+
if (!Bootstrap::containsChildren($layoutPath)) {
|
|
1155
|
+
Bootstrap::$isChildContentIncluded = true;
|
|
1156
|
+
}
|
|
1014
1157
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1158
|
+
ob_start();
|
|
1159
|
+
require_once $layoutPath;
|
|
1160
|
+
MainLayout::$children = ob_get_clean();
|
|
1161
|
+
}
|
|
1018
1162
|
}
|
|
1019
1163
|
} else {
|
|
1020
1164
|
ob_start();
|
|
1021
1165
|
require_once APP_PATH . '/not-found.php';
|
|
1022
|
-
MainLayout::$
|
|
1166
|
+
MainLayout::$children = ob_get_clean();
|
|
1023
1167
|
|
|
1024
1168
|
http_response_code(404);
|
|
1025
1169
|
CacheHandler::$isCacheable = false;
|
|
1026
1170
|
}
|
|
1027
1171
|
|
|
1028
|
-
if (Bootstrap::$isParentLayout && !empty(Bootstrap::$contentToInclude)) {
|
|
1029
|
-
ob_start();
|
|
1030
|
-
require_once Bootstrap::$contentToInclude;
|
|
1031
|
-
MainLayout::$childLayoutChildren = ob_get_clean();
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
1172
|
if (!Bootstrap::$isContentIncluded && !Bootstrap::$isChildContentIncluded) {
|
|
1035
1173
|
if (!Bootstrap::$secondRequestC69CD) {
|
|
1036
1174
|
Bootstrap::createUpdateRequestData();
|
|
@@ -1042,7 +1180,7 @@ try {
|
|
|
1042
1180
|
if (file_exists($file)) {
|
|
1043
1181
|
ob_start();
|
|
1044
1182
|
require_once $file;
|
|
1045
|
-
MainLayout::$
|
|
1183
|
+
MainLayout::$children .= ob_get_clean();
|
|
1046
1184
|
}
|
|
1047
1185
|
}
|
|
1048
1186
|
}
|
|
@@ -1059,10 +1197,15 @@ try {
|
|
|
1059
1197
|
}
|
|
1060
1198
|
}
|
|
1061
1199
|
|
|
1062
|
-
MainLayout::$children
|
|
1200
|
+
MainLayout::$children .= Bootstrap::getLoadingsFiles();
|
|
1063
1201
|
|
|
1064
1202
|
ob_start();
|
|
1065
|
-
|
|
1203
|
+
if (file_exists(Bootstrap::$parentLayoutPath)) {
|
|
1204
|
+
require_once Bootstrap::$parentLayoutPath;
|
|
1205
|
+
} else {
|
|
1206
|
+
echo MainLayout::$children;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1066
1209
|
MainLayout::$html = ob_get_clean();
|
|
1067
1210
|
MainLayout::$html = TemplateCompiler::compile(MainLayout::$html);
|
|
1068
1211
|
MainLayout::$html = TemplateCompiler::injectDynamicContent(MainLayout::$html);
|
|
@@ -1080,13 +1223,8 @@ try {
|
|
|
1080
1223
|
? Bootstrap::$parentLayoutPath
|
|
1081
1224
|
: (Bootstrap::$layoutsToInclude[0] ?? '');
|
|
1082
1225
|
|
|
1083
|
-
$message = "The layout file does not contain <?php echo MainLayout::\$
|
|
1084
|
-
$htmlMessage = "<div class='error'>The layout file does not contain <?php echo MainLayout::\$
|
|
1085
|
-
|
|
1086
|
-
if (Bootstrap::$isContentIncluded) {
|
|
1087
|
-
$message = "The parent layout file does not contain <?php echo MainLayout::\$children; ?> Or <?= MainLayout::\$children ?><br><strong>$layoutPath</strong>";
|
|
1088
|
-
$htmlMessage = "<div class='error'>The parent layout file does not contain <?php echo MainLayout::\$children; ?> Or <?= MainLayout::\$children ?><br><strong>$layoutPath</strong></div>";
|
|
1089
|
-
}
|
|
1226
|
+
$message = "The layout file does not contain <?php echo MainLayout::\$children; ?> or <?= MainLayout::\$children ?>\n<strong>$layoutPath</strong>";
|
|
1227
|
+
$htmlMessage = "<div class='error'>The layout file does not contain <?php echo MainLayout::\$children; ?> or <?= MainLayout::\$children ?><br><strong>$layoutPath</strong></div>";
|
|
1090
1228
|
|
|
1091
1229
|
$errorDetails = Bootstrap::isAjaxOrXFileRequestOrRouteFile() ? $message : $htmlMessage;
|
|
1092
1230
|
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","docker-compose.yml","Dockerfile"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+8).replace(/\\/g,"\\\\"),c=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let o=`http://localhost/${c}`;o=o.endsWith("/")?o.slice(0,-1):o;const n=o.replace(/(?<!:)(\/\/+)/g,"/"),r=c.replace(/\/\/+/g,"/");return{bsTarget:`${n}/`,bsPathRewrite:{"^/":`/${r.startsWith("/")?r.substring(1):r}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const c=JSON.parse(fs.readFileSync(t,"utf8"));c.scripts={...c.scripts,projectName:"tsx settings/project-name.ts"};let o=[];if(s.tailwindcss&&(c.scripts={...c.scripts,tailwind:"postcss src/app/globals.css -o public/css/styles.css --watch","tailwind:build":"postcss src/app/globals.css -o public/css/styles.css"},o.push("tailwind")),s.typescript&&!s.backendOnly&&(c.scripts={...c.scripts,"ts:watch":"vite build --watch","ts:build":"vite build"},o.push("ts:watch")),s.websocket&&(c.scripts={...c.scripts,websocket:"tsx settings/restart-websocket.ts"},o.push("websocket")),s.mcp&&(c.scripts={...c.scripts,mcp:"tsx settings/restart-mcp.ts"},o.push("mcp")),s.docker&&(c.scripts={...c.scripts,docker:"docker-compose up"},o.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";c.scripts={...c.scripts,"create-swagger-docs":e}}let n={...c.scripts};n.browserSync="tsx settings/bs-config.ts",n["browserSync:build"]="tsx settings/build.ts",n.dev=`npm-run-all projectName -p browserSync ${o.join(" ")}`;let r=["browserSync:build"];s.tailwindcss&&r.unshift("tailwind:build"),s.typescript&&!s.backendOnly&&r.unshift("ts:build"),n.build=`npm-run-all ${r.join(" ")}`,c.scripts=n,c.type="module",fs.writeFileSync(t,JSON.stringify(c,null,2))}async function updateComposerJson(e){checkExcludeFiles(path.join(e,"composer.json"))}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"public","js","main.js");if(checkExcludeFiles(t))return;let c=fs.readFileSync(t,"utf8");c+='\nwindow.ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,c,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const c=fs.existsSync(e),o=c&&fs.statSync(e);if(c&&o&&o.isDirectory()){const c=s.toLowerCase();if(!t.websocket&&c.includes("src\\lib\\websocket"))return;if(!t.mcp&&c.includes("src\\lib\\mcp"))return;if((!t.typescript||t.backendOnly)&&(c.endsWith("\\ts")||c.includes("\\ts\\")))return;if(t.backendOnly&&c.includes("public\\js")||t.backendOnly&&c.includes("public\\css")||t.backendOnly&&c.includes("public\\assets"))return;if(!t.swaggerDocs&&c.includes("src\\app\\swagger-docs"))return;const o=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(o))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach(c=>{copyRecursiveSync(path.join(e,c),path.join(s,c),t)})}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("globals.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.mcp&&s.includes("restart-mcp.ts"))return;if(!t.docker&&dockerFiles.some(e=>s.includes(e)))return;if(t.backendOnly&&nonBackendFiles.some(e=>s.includes(e)))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach(({src:s,dest:c})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,c),t)})}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),c="";s.backendOnly||(s.tailwindcss||(c='\n <link href="/css/index.css" rel="stylesheet" />'),c+='\n <script type="module" src="/js/main.js"><\/script>');let o="";s.backendOnly||(o=s.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" /> ${c}`:c),e=e.replace("</head>",`${o}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.php:"),e)}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.typescript&&!s.backendOnly&&t.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const c=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"}];s.typescript&&!s.backendOnly&&c.push({src:"/ts",dest:"/ts"}),s.docker&&c.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach(({src:s,dest:t})=>{const c=path.join(__dirname,s),o=path.join(e,t);if(checkExcludeFiles(o))return;const n=fs.readFileSync(c,"utf8");fs.writeFileSync(o,n,{flag:"w"})}),await executeCopy(e,c,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const o=generateAuthSecret(),n=generateHexEncodedKey(),r=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${o}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${n}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"`;if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${r}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,r)}async function getAnswer(e={}){if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const c={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,docker:t.features.docker??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1,typescript:t.features.typescript??!1},o=process.argv.slice(2);return o.includes("--backend-only")&&(c.backendOnly=!0),o.includes("--swagger-docs")&&(c.swaggerDocs=!0),o.includes("--tailwindcss")&&(c.tailwindcss=!0),o.includes("--websocket")&&(c.websocket=!0),o.includes("--mcp")&&(c.mcp=!0),o.includes("--prisma")&&(c.prisma=!0),o.includes("--docker")&&(c.docker=!0),o.includes("--typescript")&&(c.typescript=!0),c}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1,typescript:!1},c=process.argv.slice(2);return c.includes("--backend-only")&&(t.backendOnly=!0),c.includes("--swagger-docs")&&(t.swaggerDocs=!0),c.includes("--tailwindcss")&&(t.tailwindcss=!0),c.includes("--websocket")&&(t.websocket=!0),c.includes("--mcp")&&(t.mcp=!0),c.includes("--prisma")&&(t.prisma=!0),c.includes("--docker")&&(t.docker=!0),c.includes("--typescript")&&(t.typescript=!0),t}}const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},c=await prompts(s,{onCancel:t}),o=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||o.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||o.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||o.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||o.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const n=await prompts(o,{onCancel:t});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:n.swaggerDocs??e.swaggerDocs??!1,tailwindcss:n.tailwindcss??e.tailwindcss??!1,typescript:n.typescript??e.typescript??!1,websocket:n.websocket??e.websocket??!1,mcp:n.mcp??e.mcp??!1,prisma:n.prisma??e.prisma??!1,docker:n.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){console.log("Uninstalling Node dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const c=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(c,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){console.log("Uninstalling Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,e=>{let c="";e.on("data",e=>c+=e),e.on("end",()=>{try{const e=JSON.parse(c);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}})}).on("error",e=>t(e))})}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),c=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>c[e])return 1;if(t[e]<c[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:(console.error(`Package ${e} is not installed`),null)}catch(e){return console.error(e instanceof Error?e.message:String(e)),null}}async function installNpmDependencies(e,s,t=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync("npm init -y",{stdio:"inherit",cwd:e}),console.log((t?"Installing development dependencies":"Installing dependencies")+":"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const c=`npm install ${t?"--save-dev":""} ${s.join(" ")}`;execSync(c,{stdio:"inherit",cwd:e})}function getComposerCmd(){try{return execSync("composer --version",{stdio:"ignore"}),console.log("✓ Using global composer command"),{cmd:"composer",baseArgs:[]}}catch{const e="C:\\xampp\\php\\php.exe",s="C:\\ProgramData\\ComposerSetup\\bin\\composer.phar";if(!fs.existsSync(e))throw console.error(`✗ PHP not found at ${e}`),new Error(`PHP executable not found at ${e}`);if(!fs.existsSync(s))throw console.error(`✗ Composer not found at ${s}`),new Error(`Composer phar not found at ${s}`);return console.log("✓ Using XAMPP PHP with Composer phar"),{cmd:e,baseArgs:[s]}}}export async function installComposerDependencies(e,s){const{cmd:t,baseArgs:c}=getComposerCmd(),o=path.join(e,"composer.json"),n=fs.existsSync(o);if(console.log(chalk.green("Composer project initialization: "+(n?"Updating existing project…":"Setting up new project…"))),fs.existsSync(e)||(console.log(`Creating base directory: ${e}`),fs.mkdirSync(e,{recursive:!0})),!n){const s=[...c,"init","--no-interaction","--name","tsnc/prisma-php-app","--require","php:^8.2","--type","project","--version","1.0.0"];console.log("Attempting composer init...");const n=spawnSync(t,s,{cwd:e,stdio:["ignore","pipe","pipe"],encoding:"utf8"}),r=fs.existsSync(o);if(0===n.status&&r)console.log("✓ Composer init successful and composer.json created");else{0!==n.status?(console.log(`Composer init failed with status ${n.status}`),n.stderr&&console.log(`Stderr: ${n.stderr}`)):console.log("Composer init reported success but didn't create composer.json"),console.log("Creating composer.json manually...");const s={name:"tsnc/prisma-php-app",type:"project",version:"1.0.0",require:{php:"^8.2"},autoload:{"psr-4":{"":"src/"}}};try{const t=path.resolve(e,"composer.json");if(console.log(`Writing composer.json to: ${t}`),fs.writeFileSync(t,JSON.stringify(s,null,2),{encoding:"utf8"}),!fs.existsSync(t))throw new Error("File creation appeared to succeed but file doesn't exist");console.log("✓ Successfully created composer.json")}catch(s){if(console.error("✗ Failed to create composer.json:",s),console.error(`Base directory: ${e}`),console.error(`Absolute base directory: ${path.resolve(e)}`),console.error(`Target file path: ${o}`),console.error(`Absolute target file path: ${path.resolve(o)}`),console.error(`Current working directory: ${process.cwd()}`),console.error(`Base directory exists: ${fs.existsSync(e)}`),fs.existsSync(e))try{const s=fs.statSync(e);console.error(`Base directory is writable: ${s.isDirectory()}`)}catch(e){console.error(`Cannot stat base directory: ${e}`)}throw new Error(`Cannot create composer.json: ${s}`)}}}const r=path.resolve(e,"composer.json");if(!fs.existsSync(r))throw console.error(`✗ composer.json still not found at ${r}`),console.error("Directory contents:",fs.readdirSync(e)),new Error("Failed to create composer.json - file does not exist after all attempts");let i;try{const e=fs.readFileSync(r,"utf8");console.log("✓ Successfully read composer.json"),i=JSON.parse(e)}catch(e){throw console.error("✗ Failed to read/parse composer.json:",e),new Error(`Cannot read composer.json: ${e}`)}i.autoload??={},i.autoload["psr-4"]??={},i.autoload["psr-4"][""]??="src/";try{fs.writeFileSync(r,JSON.stringify(i,null,2)),console.log("✓ Updated composer.json with PSR-4 autoload")}catch(e){throw console.error("✗ Failed to update composer.json:",e),e}if(s.length){console.log("Installing Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));try{const o=`${t} ${[...c,"require","--no-interaction","-W",...s].join(" ")}`;execSync(o,{stdio:"inherit",cwd:e,env:{...process.env}}),console.log("✓ Composer dependencies installed")}catch(e){throw console.error("✗ Failed to install composer dependencies:",e),e}}if(n)try{execSync(`${t} ${[...c,"update","--lock","--no-install","--no-interaction"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer lock updated")}catch(e){throw console.error("✗ Failed to update composer lock:",e),e}try{execSync(`${t} ${[...c,"dump-autoload","--quiet"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer autoloader regenerated")}catch(e){throw console.error("✗ Failed to regenerate autoloader:",e),e}}const npmPinnedVersions={"@tailwindcss/postcss":"^4.1.17","@types/browser-sync":"^2.29.1","@types/node":"^24.10.1","@types/prompts":"^2.4.9","browser-sync":"^3.0.4",chalk:"^5.6.2","chokidar-cli":"^3.0.0",cssnano:"^7.1.2","http-proxy-middleware":"^3.0.5","npm-run-all":"^4.1.5","php-parser":"^3.2.5",postcss:"^8.5.6","postcss-cli":"^11.0.1",prompts:"^2.4.2",tailwindcss:"^4.1.17",tsx:"^4.20.6",typescript:"^5.9.3",vite:"^7.2.2","fast-glob":"^3.3.3"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}const composerPinnedVersions={"vlucas/phpdotenv":"^5.6.2","firebase/php-jwt":"^6.11.1","phpmailer/phpmailer":"^7.0.0","guzzlehttp/guzzle":"^7.10.0","symfony/uid":"^7.3.1","brick/math":"^0.14.0","cboden/ratchet":"^0.4.4","tsnc/prisma-php":"^1.0.0","php-mcp/server":"3.3.0","gehrisandro/tailwind-merge-php":"^1.1.0"};function composerPkg(e){return composerPinnedVersions[e]?`${e}:${composerPinnedVersions[e]}`:e}async function setupStarterKit(e,s){if(!s.starterKit)return;let t=null;if(STARTER_KITS[s.starterKit]?t=STARTER_KITS[s.starterKit]:s.starterKitSource&&(t={id:s.starterKit,name:`Custom Starter Kit (${s.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:s.starterKitSource}}),t){if(console.log(chalk.green(`Setting up ${t.name}...`)),t.source)try{const c=t.source.branch?`git clone -b ${t.source.branch} --depth 1 ${t.source.url} ${e}`:`git clone --depth 1 ${t.source.url} ${e}`;execSync(c,{stdio:"inherit"});const o=path.join(e,".git");fs.existsSync(o)&&fs.rmSync(o,{recursive:!0,force:!0}),console.log(chalk.blue("Starter kit cloned successfully!"));const n=path.join(e,"prisma-php.json");if(fs.existsSync(n))try{const t=JSON.parse(fs.readFileSync(n,"utf8")),c=e.replace(/\\/g,"\\"),o=bsConfigUrls(c);t.projectName=s.projectName,t.projectRootPath=c,t.bsTarget=o.bsTarget,t.bsPathRewrite=o.bsPathRewrite;const r=await fetchPackageVersion("create-prisma-php-app");t.version=t.version||r,fs.writeFileSync(n,JSON.stringify(t,null,2)),console.log(chalk.green("Updated prisma-php.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update prisma-php.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}t.customSetup&&await t.customSetup(e,s),console.log(chalk.green(`✓ ${t.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${s.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\n🚀 Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const s=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(s)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const t=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");t&&console.log(chalk.magenta(` Features: ${t}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-prisma-php-app my-project --starter-kit=basic"),console.log(" npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}async function main(){try{const e=process.argv.slice(2);let s=e[0];const t=e.find(e=>e.startsWith("--starter-kit=")),c=t?.split("=")[1],o=e.find(e=>e.startsWith("--starter-kit-source=")),n=o?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let r=null,i=!1;if(s){const t=process.cwd(),o=path.join(t,"prisma-php.json");if(c&&n){i=!0;const t={projectName:s,starterKit:c,starterKitSource:n,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma"),docker:e.includes("--docker")};r=await getAnswer(t)}else if(fs.existsSync(o)){const c=readJsonFile(o);let n=[];c.excludeFiles?.map(e=>{const s=path.join(t,e);fs.existsSync(s)&&n.push(s.replace(/\\/g,"/"))}),updateAnswer={projectName:s,backendOnly:c.backendOnly,swaggerDocs:c.swaggerDocs,tailwindcss:c.tailwindcss,websocket:c.websocket,mcp:c.mcp,prisma:c.prisma,docker:c.docker,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:n??[],filePath:t};const i={projectName:s,backendOnly:e.includes("--backend-only")||c.backendOnly,swaggerDocs:e.includes("--swagger-docs")||c.swaggerDocs,tailwindcss:e.includes("--tailwindcss")||c.tailwindcss,websocket:e.includes("--websocket")||c.websocket,prisma:e.includes("--prisma")||c.prisma,docker:e.includes("--docker")||c.docker,mcp:e.includes("--mcp")||c.mcp};r=await getAnswer(i),null!==r&&(updateAnswer={projectName:s,backendOnly:r.backendOnly,swaggerDocs:r.swaggerDocs,tailwindcss:r.tailwindcss,websocket:r.websocket,mcp:r.mcp,prisma:r.prisma,docker:r.docker,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:n??[],filePath:t})}else{const t={projectName:s,starterKit:c,starterKitSource:n,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma"),docker:e.includes("--docker")};r=await getAnswer(t)}if(null===r)return void console.log(chalk.red("Installation cancelled."))}else r=await getAnswer();if(null===r)return void console.warn(chalk.red("Installation cancelled."));const a=await fetchPackageVersion("create-prisma-php-app"),p=getInstalledPackageVersion("create-prisma-php-app");p?-1===compareVersions(p,a)&&(execSync("npm uninstall -g create-prisma-php-app",{stdio:"inherit"}),execSync("npm install -g create-prisma-php-app",{stdio:"inherit"})):execSync("npm install -g create-prisma-php-app",{stdio:"inherit"});const l=process.cwd();let d;if(s)if(i){const t=path.join(l,s);fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0}),d=t,await setupStarterKit(d,r),process.chdir(d);const c=path.join(d,"prisma-php.json");if(fs.existsSync(c)){const s=JSON.parse(fs.readFileSync(c,"utf8"));e.includes("--backend-only")&&(s.backendOnly=!0),e.includes("--swagger-docs")&&(s.swaggerDocs=!0),e.includes("--tailwindcss")&&(s.tailwindcss=!0),e.includes("--websocket")&&(s.websocket=!0),e.includes("--mcp")&&(s.mcp=!0),e.includes("--prisma")&&(s.prisma=!0),e.includes("--docker")&&(s.docker=!0),r={...r,backendOnly:s.backendOnly,swaggerDocs:s.swaggerDocs,tailwindcss:s.tailwindcss,websocket:s.websocket,mcp:s.mcp,prisma:s.prisma,docker:s.docker};let t=[];s.excludeFiles?.map(e=>{const s=path.join(d,e);fs.existsSync(s)&&t.push(s.replace(/\\/g,"/"))}),updateAnswer={...r,isUpdate:!0,componentScanDirs:s.componentScanDirs??[],excludeFiles:s.excludeFiles??[],excludeFilePath:t??[],filePath:d}}}else{const e=path.join(l,"prisma-php.json"),t=path.join(l,s),c=path.join(t,"prisma-php.json");fs.existsSync(e)?d=l:fs.existsSync(t)&&fs.existsSync(c)?(d=t,process.chdir(t)):(fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0}),d=t,process.chdir(t))}else fs.mkdirSync(r.projectName,{recursive:!0}),d=path.join(l,r.projectName),process.chdir(r.projectName);let u=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("php-parser")],g=[composerPkg("vlucas/phpdotenv"),composerPkg("firebase/php-jwt"),composerPkg("phpmailer/phpmailer"),composerPkg("guzzlehttp/guzzle"),composerPkg("symfony/uid"),composerPkg("brick/math"),composerPkg("tsnc/prisma-php")];if(r.swaggerDocs&&u.push(npmPkg("swagger-jsdoc"),npmPkg("@types/swagger-jsdoc")),r.swaggerDocs&&r.prisma&&u.push(npmPkg("prompts"),npmPkg("@types/prompts")),r.tailwindcss&&(u.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano")),g.push("gehrisandro/tailwind-merge-php")),r.websocket&&g.push("cboden/ratchet"),r.mcp&&g.push("php-mcp/server"),r.prisma&&execSync("npm install -g prisma-client-php",{stdio:"inherit"}),r.typescript&&!r.backendOnly&&u.push(npmPkg("vite"),npmPkg("fast-glob")),r.starterKit&&!i&&await setupStarterKit(d,r),await installNpmDependencies(d,u,!0),await installComposerDependencies(d,g),s||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(d,r),r.prisma&&execSync("npx ppo init --prisma-php",{stdio:"inherit"}),r.swaggerDocs){const e=path.join(d,"src","app","swagger-docs"),s=path.join(e,"apis"),t=path.join(d,"public","assets"),c=path.join(t,"dist");fs.existsSync(e)&&fs.readdirSync(e).length>0&&(console.log("Removing existing swagger-docs directory..."),fs.rmSync(e,{recursive:!0,force:!0})),console.log(chalk.blue("Cloning swagger-docs repository...")),execSync(`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${e}`,{stdio:"inherit"});const o=path.join(e,".git");fs.existsSync(o)&&fs.rmSync(o,{recursive:!0,force:!0}),fs.existsSync(t)||(console.log(chalk.blue("Creating public/assets directory...")),fs.mkdirSync(t,{recursive:!0}));const n=path.join(e,"dist");fs.existsSync(n)?(console.log(chalk.blue("Moving dist folder to public/assets/dist...")),fs.existsSync(c)&&fs.rmSync(c,{recursive:!0,force:!0}),fs.renameSync(n,c),console.log(chalk.green("✓ Moved dist to public/assets/dist"))):console.warn(chalk.yellow("Warning: dist folder not found in cloned repository")),fs.existsSync(s)?console.log(chalk.green("✓ APIs folder preserved in src/app/swagger-docs/apis")):console.warn(chalk.yellow("Warning: apis folder not found in cloned repository")),console.log(chalk.green("✓ Swagger docs setup complete"))}if(updateAnswer?.isUpdate){const e=[],s=[],t=e=>{try{const s=path.join(d,"composer.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!(!t.require||!t.require[e])}return!1}catch{return!1}},c=e=>{try{const s=path.join(d,"package.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!!(t.dependencies&&t.dependencies[e]||t.devDependencies&&t.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const s=path.join(d,"src","app",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const s=path.join(d,"src","app",e);fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.swaggerDocs){const s=path.join(d,"src","app","swagger-docs");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("swagger-docs was deleted successfully."));["swagger-config.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))}),c("swagger-jsdoc")&&e.push("swagger-jsdoc"),c("@types/swagger-jsdoc")&&e.push("@types/swagger-jsdoc"),c("prompts")&&e.push("prompts"),c("@types/prompts")&&e.push("@types/prompts")}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano"].forEach(s=>{c(s)&&e.push(s)});const o="gehrisandro/tailwind-merge-php";t(o)&&s.push(o)}if(!updateAnswer.websocket){["restart-websocket.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(d,"src","Lib","Websocket");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("Websocket folder was deleted successfully.")),t("cboden/ratchet")&&s.push("cboden/ratchet")}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(d,"src","Lib","MCP");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),t("php-mcp/server")&&s.push("php-mcp/server")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals"].forEach(s=>{c(s)&&e.push(s)})}if(!updateAnswer.docker){[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const s=path.join(d,"ts");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));["vite","fast-glob"].forEach(s=>{c(s)&&e.push(s)})}const o=e=>Array.from(new Set(e)),n=o(e),r=o(s);n.length>0&&(console.log(`Uninstalling npm packages: ${n.join(", ")}`),await uninstallNpmDependencies(d,n,!0)),r.length>0&&(console.log(`Uninstalling composer packages: ${r.join(", ")}`),await uninstallComposerDependencies(d,r))}if(!i||!fs.existsSync(path.join(d,"prisma-php.json"))){const e=d.replace(/\\/g,"\\"),s=bsConfigUrls(e),t={projectName:r.projectName,projectRootPath:e,phpEnvironment:"XAMPP",phpRootPathExe:"C:\\xampp\\php\\php.exe",bsTarget:s.bsTarget,bsPathRewrite:s.bsPathRewrite,backendOnly:r.backendOnly,swaggerDocs:r.swaggerDocs,tailwindcss:r.tailwindcss,websocket:r.websocket,mcp:r.mcp,prisma:r.prisma,docker:r.docker,typescript:r.typescript,version:a,componentScanDirs:updateAnswer?.componentScanDirs??["src","vendor/tsnc/prisma-php/src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(d,"prisma-php.json"),JSON.stringify(t,null,2),{flag:"w"})}execSync(updateAnswer?.isUpdate?"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update":"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install",{stdio:"inherit"}),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Prisma PHP project successfully created in ${chalk.green(d.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
|
|
2
|
+
import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","docker-compose.yml","Dockerfile"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+8).replace(/\\/g,"\\\\"),c=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let o=`http://localhost/${c}`;o=o.endsWith("/")?o.slice(0,-1):o;const n=o.replace(/(?<!:)(\/\/+)/g,"/"),r=c.replace(/\/\/+/g,"/");return{bsTarget:`${n}/`,bsPathRewrite:{"^/":`/${r.startsWith("/")?r.substring(1):r}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const c=JSON.parse(fs.readFileSync(t,"utf8"));c.scripts={...c.scripts,projectName:"tsx settings/project-name.ts"};let o=[];if(s.tailwindcss&&(c.scripts={...c.scripts,tailwind:"postcss src/app/globals.css -o public/css/styles.css --watch","tailwind:build":"postcss src/app/globals.css -o public/css/styles.css"},o.push("tailwind")),s.typescript&&!s.backendOnly&&(c.scripts={...c.scripts,"ts:watch":"vite build --watch","ts:build":"vite build"},o.push("ts:watch")),s.websocket&&(c.scripts={...c.scripts,websocket:"tsx settings/restart-websocket.ts"},o.push("websocket")),s.mcp&&(c.scripts={...c.scripts,mcp:"tsx settings/restart-mcp.ts"},o.push("mcp")),s.docker&&(c.scripts={...c.scripts,docker:"docker-compose up"},o.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";c.scripts={...c.scripts,"create-swagger-docs":e}}let n={...c.scripts};n.browserSync="tsx settings/bs-config.ts",n["browserSync:build"]="tsx settings/build.ts",n.dev=`npm-run-all projectName -p browserSync ${o.join(" ")}`;let r=["browserSync:build"];s.tailwindcss&&r.unshift("tailwind:build"),s.typescript&&!s.backendOnly&&r.unshift("ts:build"),n.build=`npm-run-all ${r.join(" ")}`,c.scripts=n,c.type="module",fs.writeFileSync(t,JSON.stringify(c,null,2))}async function updateComposerJson(e){checkExcludeFiles(path.join(e,"composer.json"))}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"public","js","main.js");if(checkExcludeFiles(t))return;let c=fs.readFileSync(t,"utf8");c+='\nwindow.ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,c,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const c=fs.existsSync(e),o=c&&fs.statSync(e);if(c&&o&&o.isDirectory()){const c=s.toLowerCase();if(!t.websocket&&c.includes("src\\lib\\websocket"))return;if(!t.mcp&&c.includes("src\\lib\\mcp"))return;if((!t.typescript||t.backendOnly)&&(c.endsWith("\\ts")||c.includes("\\ts\\")))return;if(t.backendOnly&&c.includes("public\\js")||t.backendOnly&&c.includes("public\\css")||t.backendOnly&&c.includes("public\\assets"))return;if(!t.swaggerDocs&&c.includes("src\\app\\swagger-docs"))return;const o=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(o))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach(c=>{copyRecursiveSync(path.join(e,c),path.join(s,c),t)})}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("globals.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.mcp&&s.includes("restart-mcp.ts"))return;if(!t.docker&&dockerFiles.some(e=>s.includes(e)))return;if(t.backendOnly&&nonBackendFiles.some(e=>s.includes(e)))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach(({src:s,dest:c})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,c),t)})}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),c="";s.backendOnly||(s.tailwindcss||(c='\n <link href="/css/index.css" rel="stylesheet" />'),c+='\n <script type="module" src="/js/main.js"><\/script>');let o="";s.backendOnly||(o=s.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" /> ${c}`:c),e=e.replace("</head>",`${o}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.php:"),e)}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.typescript&&!s.backendOnly&&t.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const c=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"}];s.typescript&&!s.backendOnly&&c.push({src:"/ts",dest:"/ts"}),s.docker&&c.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach(({src:s,dest:t})=>{const c=path.join(__dirname,s),o=path.join(e,t);if(checkExcludeFiles(o))return;const n=fs.readFileSync(c,"utf8");fs.writeFileSync(o,n,{flag:"w"})}),await executeCopy(e,c,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const o=generateAuthSecret(),n=generateHexEncodedKey(),r=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${o}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${n}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"`;if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${r}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,r)}async function getAnswer(e={}){if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const c={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,docker:t.features.docker??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1,typescript:t.features.typescript??!1},o=process.argv.slice(2);return o.includes("--backend-only")&&(c.backendOnly=!0),o.includes("--swagger-docs")&&(c.swaggerDocs=!0),o.includes("--tailwindcss")&&(c.tailwindcss=!0),o.includes("--websocket")&&(c.websocket=!0),o.includes("--mcp")&&(c.mcp=!0),o.includes("--prisma")&&(c.prisma=!0),o.includes("--docker")&&(c.docker=!0),o.includes("--typescript")&&(c.typescript=!0),c}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1,typescript:!1},c=process.argv.slice(2);return c.includes("--backend-only")&&(t.backendOnly=!0),c.includes("--swagger-docs")&&(t.swaggerDocs=!0),c.includes("--tailwindcss")&&(t.tailwindcss=!0),c.includes("--websocket")&&(t.websocket=!0),c.includes("--mcp")&&(t.mcp=!0),c.includes("--prisma")&&(t.prisma=!0),c.includes("--docker")&&(t.docker=!0),c.includes("--typescript")&&(t.typescript=!0),t}}const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},c=await prompts(s,{onCancel:t}),o=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||o.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||o.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||o.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||o.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const n=await prompts(o,{onCancel:t});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:n.swaggerDocs??e.swaggerDocs??!1,tailwindcss:n.tailwindcss??e.tailwindcss??!1,typescript:n.typescript??e.typescript??!1,websocket:n.websocket??e.websocket??!1,mcp:n.mcp??e.mcp??!1,prisma:n.prisma??e.prisma??!1,docker:n.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){console.log("Uninstalling Node dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const c=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(c,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){console.log("Uninstalling Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,e=>{let c="";e.on("data",e=>c+=e),e.on("end",()=>{try{const e=JSON.parse(c);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}})}).on("error",e=>t(e))})}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),c=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>c[e])return 1;if(t[e]<c[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:(console.error(`Package ${e} is not installed`),null)}catch(e){return console.error(e instanceof Error?e.message:String(e)),null}}async function installNpmDependencies(e,s,t=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync("npm init -y",{stdio:"inherit",cwd:e}),console.log((t?"Installing development dependencies":"Installing dependencies")+":"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const c=`npm install ${t?"--save-dev":""} ${s.join(" ")}`;execSync(c,{stdio:"inherit",cwd:e})}function getComposerCmd(){try{return execSync("composer --version",{stdio:"ignore"}),console.log("✓ Using global composer command"),{cmd:"composer",baseArgs:[]}}catch{const e="C:\\xampp\\php\\php.exe",s="C:\\ProgramData\\ComposerSetup\\bin\\composer.phar";if(!fs.existsSync(e))throw console.error(`✗ PHP not found at ${e}`),new Error(`PHP executable not found at ${e}`);if(!fs.existsSync(s))throw console.error(`✗ Composer not found at ${s}`),new Error(`Composer phar not found at ${s}`);return console.log("✓ Using XAMPP PHP with Composer phar"),{cmd:e,baseArgs:[s]}}}export async function installComposerDependencies(e,s){const{cmd:t,baseArgs:c}=getComposerCmd(),o=path.join(e,"composer.json"),n=fs.existsSync(o);if(console.log(chalk.green("Composer project initialization: "+(n?"Updating existing project…":"Setting up new project…"))),fs.existsSync(e)||(console.log(`Creating base directory: ${e}`),fs.mkdirSync(e,{recursive:!0})),!n){const s=[...c,"init","--no-interaction","--name","tsnc/prisma-php-app","--require","php:^8.2","--type","project","--version","1.0.0"];console.log("Attempting composer init...");const n=spawnSync(t,s,{cwd:e,stdio:["ignore","pipe","pipe"],encoding:"utf8"}),r=fs.existsSync(o);if(0===n.status&&r)console.log("✓ Composer init successful and composer.json created");else{0!==n.status?(console.log(`Composer init failed with status ${n.status}`),n.stderr&&console.log(`Stderr: ${n.stderr}`)):console.log("Composer init reported success but didn't create composer.json"),console.log("Creating composer.json manually...");const s={name:"tsnc/prisma-php-app",type:"project",version:"1.0.0",require:{php:"^8.2"},autoload:{"psr-4":{"":"src/"}}};try{const t=path.resolve(e,"composer.json");if(console.log(`Writing composer.json to: ${t}`),fs.writeFileSync(t,JSON.stringify(s,null,2),{encoding:"utf8"}),!fs.existsSync(t))throw new Error("File creation appeared to succeed but file doesn't exist");console.log("✓ Successfully created composer.json")}catch(s){if(console.error("✗ Failed to create composer.json:",s),console.error(`Base directory: ${e}`),console.error(`Absolute base directory: ${path.resolve(e)}`),console.error(`Target file path: ${o}`),console.error(`Absolute target file path: ${path.resolve(o)}`),console.error(`Current working directory: ${process.cwd()}`),console.error(`Base directory exists: ${fs.existsSync(e)}`),fs.existsSync(e))try{const s=fs.statSync(e);console.error(`Base directory is writable: ${s.isDirectory()}`)}catch(e){console.error(`Cannot stat base directory: ${e}`)}throw new Error(`Cannot create composer.json: ${s}`)}}}const r=path.resolve(e,"composer.json");if(!fs.existsSync(r))throw console.error(`✗ composer.json still not found at ${r}`),console.error("Directory contents:",fs.readdirSync(e)),new Error("Failed to create composer.json - file does not exist after all attempts");let i;try{const e=fs.readFileSync(r,"utf8");console.log("✓ Successfully read composer.json"),i=JSON.parse(e)}catch(e){throw console.error("✗ Failed to read/parse composer.json:",e),new Error(`Cannot read composer.json: ${e}`)}i.autoload??={},i.autoload["psr-4"]??={},i.autoload["psr-4"][""]??="src/";try{fs.writeFileSync(r,JSON.stringify(i,null,2)),console.log("✓ Updated composer.json with PSR-4 autoload")}catch(e){throw console.error("✗ Failed to update composer.json:",e),e}if(s.length){console.log("Installing Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));try{const o=`${t} ${[...c,"require","--no-interaction","-W",...s].join(" ")}`;execSync(o,{stdio:"inherit",cwd:e,env:{...process.env}}),console.log("✓ Composer dependencies installed")}catch(e){throw console.error("✗ Failed to install composer dependencies:",e),e}}if(n)try{execSync(`${t} ${[...c,"update","--lock","--no-install","--no-interaction"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer lock updated")}catch(e){throw console.error("✗ Failed to update composer lock:",e),e}try{execSync(`${t} ${[...c,"dump-autoload","--quiet"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer autoloader regenerated")}catch(e){throw console.error("✗ Failed to regenerate autoloader:",e),e}}const npmPinnedVersions={"@tailwindcss/postcss":"^4.1.17","@types/browser-sync":"^2.29.1","@types/node":"^24.10.1","@types/prompts":"^2.4.9","browser-sync":"^3.0.4",chalk:"^5.6.2","chokidar-cli":"^3.0.0",cssnano:"^7.1.2","http-proxy-middleware":"^3.0.5","npm-run-all":"^4.1.5","php-parser":"^3.2.5",postcss:"^8.5.6","postcss-cli":"^11.0.1",prompts:"^2.4.2",tailwindcss:"^4.1.17",tsx:"^4.20.6",typescript:"^5.9.3",vite:"^7.2.4","fast-glob":"^3.3.3"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}const composerPinnedVersions={"vlucas/phpdotenv":"^5.6.2","firebase/php-jwt":"^6.11.1","phpmailer/phpmailer":"^7.0.0","guzzlehttp/guzzle":"^7.10.0","symfony/uid":"^7.3.1","brick/math":"^0.14.1","cboden/ratchet":"^0.4.4","tsnc/prisma-php":"^1.0.0","php-mcp/server":"3.3.0","gehrisandro/tailwind-merge-php":"^1.1.0"};function composerPkg(e){return composerPinnedVersions[e]?`${e}:${composerPinnedVersions[e]}`:e}async function setupStarterKit(e,s){if(!s.starterKit)return;let t=null;if(STARTER_KITS[s.starterKit]?t=STARTER_KITS[s.starterKit]:s.starterKitSource&&(t={id:s.starterKit,name:`Custom Starter Kit (${s.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:s.starterKitSource}}),t){if(console.log(chalk.green(`Setting up ${t.name}...`)),t.source)try{const c=t.source.branch?`git clone -b ${t.source.branch} --depth 1 ${t.source.url} ${e}`:`git clone --depth 1 ${t.source.url} ${e}`;execSync(c,{stdio:"inherit"});const o=path.join(e,".git");fs.existsSync(o)&&fs.rmSync(o,{recursive:!0,force:!0}),console.log(chalk.blue("Starter kit cloned successfully!"));const n=path.join(e,"prisma-php.json");if(fs.existsSync(n))try{const t=JSON.parse(fs.readFileSync(n,"utf8")),c=e.replace(/\\/g,"\\"),o=bsConfigUrls(c);t.projectName=s.projectName,t.projectRootPath=c,t.bsTarget=o.bsTarget,t.bsPathRewrite=o.bsPathRewrite;const r=await fetchPackageVersion("create-prisma-php-app");t.version=t.version||r,fs.writeFileSync(n,JSON.stringify(t,null,2)),console.log(chalk.green("Updated prisma-php.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update prisma-php.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}t.customSetup&&await t.customSetup(e,s),console.log(chalk.green(`✓ ${t.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${s.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\n🚀 Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const s=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(s)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const t=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");t&&console.log(chalk.magenta(` Features: ${t}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-prisma-php-app my-project --starter-kit=basic"),console.log(" npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}async function main(){try{const e=process.argv.slice(2);let s=e[0];const t=e.find(e=>e.startsWith("--starter-kit=")),c=t?.split("=")[1],o=e.find(e=>e.startsWith("--starter-kit-source=")),n=o?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let r=null,i=!1;if(s){const t=process.cwd(),o=path.join(t,"prisma-php.json");if(c&&n){i=!0;const t={projectName:s,starterKit:c,starterKitSource:n,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma"),docker:e.includes("--docker")};r=await getAnswer(t)}else if(fs.existsSync(o)){const c=readJsonFile(o);let n=[];c.excludeFiles?.map(e=>{const s=path.join(t,e);fs.existsSync(s)&&n.push(s.replace(/\\/g,"/"))}),updateAnswer={projectName:s,backendOnly:c.backendOnly,swaggerDocs:c.swaggerDocs,tailwindcss:c.tailwindcss,websocket:c.websocket,mcp:c.mcp,prisma:c.prisma,docker:c.docker,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:n??[],filePath:t};const i={projectName:s,backendOnly:e.includes("--backend-only")||c.backendOnly,swaggerDocs:e.includes("--swagger-docs")||c.swaggerDocs,tailwindcss:e.includes("--tailwindcss")||c.tailwindcss,websocket:e.includes("--websocket")||c.websocket,prisma:e.includes("--prisma")||c.prisma,docker:e.includes("--docker")||c.docker,mcp:e.includes("--mcp")||c.mcp};r=await getAnswer(i),null!==r&&(updateAnswer={projectName:s,backendOnly:r.backendOnly,swaggerDocs:r.swaggerDocs,tailwindcss:r.tailwindcss,websocket:r.websocket,mcp:r.mcp,prisma:r.prisma,docker:r.docker,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:n??[],filePath:t})}else{const t={projectName:s,starterKit:c,starterKitSource:n,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma"),docker:e.includes("--docker")};r=await getAnswer(t)}if(null===r)return void console.log(chalk.red("Installation cancelled."))}else r=await getAnswer();if(null===r)return void console.warn(chalk.red("Installation cancelled."));const a=await fetchPackageVersion("create-prisma-php-app"),p=getInstalledPackageVersion("create-prisma-php-app");p?-1===compareVersions(p,a)&&(execSync("npm uninstall -g create-prisma-php-app",{stdio:"inherit"}),execSync("npm install -g create-prisma-php-app",{stdio:"inherit"})):execSync("npm install -g create-prisma-php-app",{stdio:"inherit"});const l=process.cwd();let d;if(s)if(i){const t=path.join(l,s);fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0}),d=t,await setupStarterKit(d,r),process.chdir(d);const c=path.join(d,"prisma-php.json");if(fs.existsSync(c)){const s=JSON.parse(fs.readFileSync(c,"utf8"));e.includes("--backend-only")&&(s.backendOnly=!0),e.includes("--swagger-docs")&&(s.swaggerDocs=!0),e.includes("--tailwindcss")&&(s.tailwindcss=!0),e.includes("--websocket")&&(s.websocket=!0),e.includes("--mcp")&&(s.mcp=!0),e.includes("--prisma")&&(s.prisma=!0),e.includes("--docker")&&(s.docker=!0),r={...r,backendOnly:s.backendOnly,swaggerDocs:s.swaggerDocs,tailwindcss:s.tailwindcss,websocket:s.websocket,mcp:s.mcp,prisma:s.prisma,docker:s.docker};let t=[];s.excludeFiles?.map(e=>{const s=path.join(d,e);fs.existsSync(s)&&t.push(s.replace(/\\/g,"/"))}),updateAnswer={...r,isUpdate:!0,componentScanDirs:s.componentScanDirs??[],excludeFiles:s.excludeFiles??[],excludeFilePath:t??[],filePath:d}}}else{const e=path.join(l,"prisma-php.json"),t=path.join(l,s),c=path.join(t,"prisma-php.json");fs.existsSync(e)?d=l:fs.existsSync(t)&&fs.existsSync(c)?(d=t,process.chdir(t)):(fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0}),d=t,process.chdir(t))}else fs.mkdirSync(r.projectName,{recursive:!0}),d=path.join(l,r.projectName),process.chdir(r.projectName);let u=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("php-parser")],g=[composerPkg("vlucas/phpdotenv"),composerPkg("firebase/php-jwt"),composerPkg("phpmailer/phpmailer"),composerPkg("guzzlehttp/guzzle"),composerPkg("symfony/uid"),composerPkg("brick/math"),composerPkg("tsnc/prisma-php")];if(r.swaggerDocs&&u.push(npmPkg("swagger-jsdoc"),npmPkg("@types/swagger-jsdoc")),r.swaggerDocs&&r.prisma&&u.push(npmPkg("prompts"),npmPkg("@types/prompts")),r.tailwindcss&&(u.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano")),g.push("gehrisandro/tailwind-merge-php")),r.websocket&&g.push("cboden/ratchet"),r.mcp&&g.push("php-mcp/server"),r.prisma&&execSync("npm install -g prisma-client-php",{stdio:"inherit"}),r.typescript&&!r.backendOnly&&u.push(npmPkg("vite"),npmPkg("fast-glob")),r.starterKit&&!i&&await setupStarterKit(d,r),await installNpmDependencies(d,u,!0),await installComposerDependencies(d,g),s||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(d,r),r.prisma&&execSync("npx ppo init --prisma-php",{stdio:"inherit"}),r.swaggerDocs){const e=path.join(d,"src","app","swagger-docs"),s=path.join(e,"apis"),t=path.join(d,"public","assets"),c=path.join(t,"dist");fs.existsSync(e)&&fs.readdirSync(e).length>0&&(console.log("Removing existing swagger-docs directory..."),fs.rmSync(e,{recursive:!0,force:!0})),console.log(chalk.blue("Cloning swagger-docs repository...")),execSync(`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${e}`,{stdio:"inherit"});const o=path.join(e,".git");fs.existsSync(o)&&fs.rmSync(o,{recursive:!0,force:!0}),fs.existsSync(t)||(console.log(chalk.blue("Creating public/assets directory...")),fs.mkdirSync(t,{recursive:!0}));const n=path.join(e,"dist");fs.existsSync(n)?(console.log(chalk.blue("Moving dist folder to public/assets/dist...")),fs.existsSync(c)&&fs.rmSync(c,{recursive:!0,force:!0}),fs.renameSync(n,c),console.log(chalk.green("✓ Moved dist to public/assets/dist"))):console.warn(chalk.yellow("Warning: dist folder not found in cloned repository")),fs.existsSync(s)?console.log(chalk.green("✓ APIs folder preserved in src/app/swagger-docs/apis")):console.warn(chalk.yellow("Warning: apis folder not found in cloned repository")),console.log(chalk.green("✓ Swagger docs setup complete"))}if(updateAnswer?.isUpdate){const e=[],s=[],t=e=>{try{const s=path.join(d,"composer.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!(!t.require||!t.require[e])}return!1}catch{return!1}},c=e=>{try{const s=path.join(d,"package.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!!(t.dependencies&&t.dependencies[e]||t.devDependencies&&t.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const s=path.join(d,"src","app",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const s=path.join(d,"src","app",e);fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.swaggerDocs){const s=path.join(d,"src","app","swagger-docs");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("swagger-docs was deleted successfully."));["swagger-config.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))}),c("swagger-jsdoc")&&e.push("swagger-jsdoc"),c("@types/swagger-jsdoc")&&e.push("@types/swagger-jsdoc"),c("prompts")&&e.push("prompts"),c("@types/prompts")&&e.push("@types/prompts")}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano"].forEach(s=>{c(s)&&e.push(s)});const o="gehrisandro/tailwind-merge-php";t(o)&&s.push(o)}if(!updateAnswer.websocket){["restart-websocket.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(d,"src","Lib","Websocket");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("Websocket folder was deleted successfully.")),t("cboden/ratchet")&&s.push("cboden/ratchet")}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const s=path.join(d,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(d,"src","Lib","MCP");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),t("php-mcp/server")&&s.push("php-mcp/server")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals"].forEach(s=>{c(s)&&e.push(s)})}if(!updateAnswer.docker){[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts"].forEach(e=>{const s=path.join(d,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const s=path.join(d,"ts");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));["vite","fast-glob"].forEach(s=>{c(s)&&e.push(s)})}const o=e=>Array.from(new Set(e)),n=o(e),r=o(s);n.length>0&&(console.log(`Uninstalling npm packages: ${n.join(", ")}`),await uninstallNpmDependencies(d,n,!0)),r.length>0&&(console.log(`Uninstalling composer packages: ${r.join(", ")}`),await uninstallComposerDependencies(d,r))}if(!i||!fs.existsSync(path.join(d,"prisma-php.json"))){const e=d.replace(/\\/g,"\\"),s=bsConfigUrls(e),t={projectName:r.projectName,projectRootPath:e,phpEnvironment:"XAMPP",phpRootPathExe:"C:\\xampp\\php\\php.exe",bsTarget:s.bsTarget,bsPathRewrite:s.bsPathRewrite,backendOnly:r.backendOnly,swaggerDocs:r.swaggerDocs,tailwindcss:r.tailwindcss,websocket:r.websocket,mcp:r.mcp,prisma:r.prisma,docker:r.docker,typescript:r.typescript,version:a,componentScanDirs:updateAnswer?.componentScanDirs??["src","vendor/tsnc/prisma-php/src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(d,"prisma-php.json"),JSON.stringify(t,null,2),{flag:"w"})}execSync(updateAnswer?.isUpdate?"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update":"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install",{stdio:"inherit"}),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Prisma PHP project successfully created in ${chalk.green(d.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
|