create-prisma-php-app 3.5.4 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.htaccess +54 -41
- package/dist/bootstrap.php +12 -7
- package/dist/index.js +41 -30
- package/dist/settings/auto-swagger-docs.ts +195 -94
- package/dist/settings/bs-config.ts +53 -58
- package/dist/settings/project-name.ts +2 -0
- package/dist/settings/restart-mcp.ts +58 -0
- package/dist/settings/restart-websocket.ts +44 -45
- package/dist/settings/utils.ts +240 -0
- package/dist/src/Lib/MCP/WeatherTools.php +104 -0
- package/dist/src/Lib/MCP/mcp-server.php +80 -0
- package/dist/src/Lib/Middleware/AuthMiddleware.php +6 -3
- package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
- package/dist/src/Lib/Websocket/websocket-server.php +105 -14
- package/package.json +1 -1
- package/dist/settings/restart-websocket.bat +0 -28
|
@@ -323,18 +323,153 @@ function generateSwaggerAnnotation(modelName: string, fields: Field[]): string {
|
|
|
323
323
|
*/`;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
function isRequiredOnCreate(field: Field): boolean {
|
|
327
|
+
// Required if Prisma says required AND no DB default AND not generated/readOnly/updatedAt/id
|
|
328
|
+
return (
|
|
329
|
+
field.isRequired &&
|
|
330
|
+
!field.hasDefaultValue &&
|
|
331
|
+
!field.isGenerated &&
|
|
332
|
+
!field.isUpdatedAt &&
|
|
333
|
+
!field.isId &&
|
|
334
|
+
!field.isReadOnly
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function phpRuleBodyForType(prismaTypeLower: string): string {
|
|
339
|
+
switch (prismaTypeLower) {
|
|
340
|
+
case "boolean":
|
|
341
|
+
return `
|
|
342
|
+
$b = Validator::boolean($v);
|
|
343
|
+
if ($b === null) return false;
|
|
344
|
+
$out = (bool)$b;
|
|
345
|
+
return true;`;
|
|
346
|
+
|
|
347
|
+
case "int":
|
|
348
|
+
case "bigint":
|
|
349
|
+
return `
|
|
350
|
+
$i = Validator::int($v);
|
|
351
|
+
if ($i === null) return false;
|
|
352
|
+
$out = $i;
|
|
353
|
+
return true;`;
|
|
354
|
+
|
|
355
|
+
case "float":
|
|
356
|
+
return `
|
|
357
|
+
$f = Validator::float($v);
|
|
358
|
+
if ($f === null) return false;
|
|
359
|
+
$out = $f;
|
|
360
|
+
return true;`;
|
|
361
|
+
|
|
362
|
+
case "decimal":
|
|
363
|
+
return `
|
|
364
|
+
$d = Validator::decimal($v);
|
|
365
|
+
if ($d === null) return false;
|
|
366
|
+
$out = (string)$d; // keep decimals canonical as string
|
|
367
|
+
return true;`;
|
|
368
|
+
|
|
369
|
+
case "datetime":
|
|
370
|
+
return `
|
|
371
|
+
$dt = Validator::dateTime($v, 'Y-m-d H:i:s');
|
|
372
|
+
if ($dt === null) return false;
|
|
373
|
+
$out = $dt;
|
|
374
|
+
return true;`;
|
|
375
|
+
|
|
376
|
+
case "json":
|
|
377
|
+
return `
|
|
378
|
+
if (is_string($v)) {
|
|
379
|
+
json_decode($v);
|
|
380
|
+
if (json_last_error() !== JSON_ERROR_NONE) return false;
|
|
381
|
+
$out = $v;
|
|
382
|
+
return true;
|
|
383
|
+
} else {
|
|
384
|
+
$enc = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
385
|
+
if ($enc === false) return false;
|
|
386
|
+
$out = $enc;
|
|
387
|
+
return true;
|
|
388
|
+
}`;
|
|
389
|
+
|
|
390
|
+
case "uuid":
|
|
391
|
+
return `
|
|
392
|
+
$s = Validator::uuid($v);
|
|
393
|
+
if ($s === null) return false;
|
|
394
|
+
$out = $s;
|
|
395
|
+
return true;`;
|
|
396
|
+
|
|
397
|
+
case "cuid":
|
|
398
|
+
return `
|
|
399
|
+
$s = Validator::cuid($v);
|
|
400
|
+
if ($s === null) return false;
|
|
401
|
+
$out = $s;
|
|
402
|
+
return true;`;
|
|
403
|
+
|
|
404
|
+
case "cuid2":
|
|
405
|
+
return `
|
|
406
|
+
$s = Validator::cuid2($v);
|
|
407
|
+
if ($s === null) return false;
|
|
408
|
+
$out = $s;
|
|
409
|
+
return true;`;
|
|
410
|
+
|
|
411
|
+
case "string":
|
|
412
|
+
default:
|
|
413
|
+
return `
|
|
414
|
+
$s = Validator::string($v, false); // trim, no HTML escaping for DB
|
|
415
|
+
if ($s === '') return false;
|
|
416
|
+
$out = $s;
|
|
417
|
+
return true;`;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function generatePhpSchema(fields: Field[], forUpdate: boolean): string {
|
|
422
|
+
const entries = fields
|
|
423
|
+
.filter((f) => !shouldSkipField(f))
|
|
424
|
+
.map((f) => {
|
|
425
|
+
const t = f.type.toLowerCase();
|
|
426
|
+
const required = forUpdate ? false : isRequiredOnCreate(f);
|
|
427
|
+
const body = phpRuleBodyForType(t).trim();
|
|
428
|
+
return ` '${f.name}' => [
|
|
429
|
+
'type' => '${t}',
|
|
430
|
+
'required' => ${required ? "true" : "false"},
|
|
431
|
+
'validate' => function($v, &$out) {
|
|
432
|
+
${body}
|
|
433
|
+
},
|
|
434
|
+
]`;
|
|
435
|
+
})
|
|
436
|
+
.join(",\n");
|
|
437
|
+
return `[\n${entries}\n]`;
|
|
438
|
+
}
|
|
329
439
|
|
|
330
|
-
|
|
440
|
+
function idValidatorSnippet(idField: Field): string {
|
|
441
|
+
const t = idField.type.toLowerCase();
|
|
442
|
+
const def = (idField as any).default?.name?.toLowerCase?.() || "";
|
|
443
|
+
|
|
444
|
+
// numeric ids (int/bigint or autoincrement())
|
|
445
|
+
if (t === "int" || t === "bigint" || def === "autoincrement") {
|
|
446
|
+
return `
|
|
447
|
+
$__id = Validator::int($id);
|
|
448
|
+
if ($__id === null) { Boom::badRequest("Invalid ${idField.name}")->toResponse(); return; }
|
|
449
|
+
$id = $__id;`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// uuid() default or explicit UUID type
|
|
453
|
+
if (t === "uuid" || def === "uuid") {
|
|
331
454
|
return `
|
|
332
|
-
if (
|
|
333
|
-
Boom::badRequest("Invalid ${idField.name}")->toResponse();
|
|
334
|
-
}`;
|
|
455
|
+
if (Validator::uuid($id) === null) { Boom::badRequest("Invalid ${idField.name}")->toResponse(); return; }`;
|
|
335
456
|
}
|
|
336
457
|
|
|
337
|
-
|
|
458
|
+
// cuid() / cuid2() defaults
|
|
459
|
+
if (def === "cuid") {
|
|
460
|
+
return `
|
|
461
|
+
if (Validator::cuid($id) === null) { Boom::badRequest("Invalid ${idField.name}")->toResponse(); return; }`;
|
|
462
|
+
}
|
|
463
|
+
if (def === "cuid2") {
|
|
464
|
+
return `
|
|
465
|
+
if (Validator::cuid2($id) === null) { Boom::badRequest("Invalid ${idField.name}")->toResponse(); return; }`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// fallback: non-empty string
|
|
469
|
+
return `
|
|
470
|
+
$__id = Validator::string($id, false);
|
|
471
|
+
if ($__id === '') { Boom::badRequest("Invalid ${idField.name}")->toResponse(); return; }
|
|
472
|
+
$id = $__id;`;
|
|
338
473
|
}
|
|
339
474
|
|
|
340
475
|
// Function to generate endpoints for a model
|
|
@@ -372,7 +507,7 @@ function generateEndpoints(modelName: string, fields: any[]): void {
|
|
|
372
507
|
const idDir = `${baseDir}/[id]`;
|
|
373
508
|
mkdirSync(resolve(__dirname, `../${idDir}`), { recursive: true });
|
|
374
509
|
const idRoutePath = `${idDir}/route.php`;
|
|
375
|
-
const
|
|
510
|
+
const idCheck = idValidatorSnippet(idField);
|
|
376
511
|
const idRouteContent = `<?php
|
|
377
512
|
|
|
378
513
|
use Lib\\Prisma\\Classes\\Prisma;
|
|
@@ -382,7 +517,7 @@ use Lib\\Request;
|
|
|
382
517
|
|
|
383
518
|
$prisma = Prisma::getInstance();
|
|
384
519
|
$id = Request::$dynamicParams->id ?? null;
|
|
385
|
-
${
|
|
520
|
+
${idCheck}
|
|
386
521
|
|
|
387
522
|
$${camelCaseModelName} = $prisma->${camelCaseModelName}->findUnique([
|
|
388
523
|
'where' => [
|
|
@@ -406,6 +541,8 @@ echo json_encode($${camelCaseModelName});`;
|
|
|
406
541
|
mkdirSync(resolve(__dirname, `../${createDir}`), { recursive: true });
|
|
407
542
|
const createRoutePath = `${createDir}/route.php`;
|
|
408
543
|
|
|
544
|
+
const createSchema = generatePhpSchema(fieldsToCreateAndUpdate, false);
|
|
545
|
+
|
|
409
546
|
const createRouteContent = `<?php
|
|
410
547
|
|
|
411
548
|
use Lib\\Prisma\\Classes\\Prisma;
|
|
@@ -415,56 +552,39 @@ use Lib\\Request;
|
|
|
415
552
|
|
|
416
553
|
$prisma = Prisma::getInstance();
|
|
417
554
|
|
|
418
|
-
|
|
419
|
-
$
|
|
420
|
-
${fieldsToCreateAndUpdate
|
|
421
|
-
.map(
|
|
422
|
-
(field) =>
|
|
423
|
-
` '${field.name}' => [
|
|
424
|
-
'type' => '${field.type.toLowerCase()}',
|
|
425
|
-
'required' => ${field.isRequired ? "true" : "false"},
|
|
426
|
-
'validate' => fn($value) => is_null($value) || $value === '' || Validator::${field.type.toLowerCase()}($value)
|
|
427
|
-
]`
|
|
428
|
-
)
|
|
429
|
-
.join(",\n")}
|
|
430
|
-
];
|
|
555
|
+
/** Schema: type-aware validate + normalize */
|
|
556
|
+
$schema = ${createSchema};
|
|
431
557
|
|
|
432
558
|
$data = [];
|
|
433
|
-
foreach ($
|
|
434
|
-
$isRequired = $
|
|
435
|
-
|
|
436
|
-
$
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Check if the field is present in the request
|
|
444
|
-
if (isset(Request::$params->$field)) {
|
|
445
|
-
$value = Request::$params->$field;
|
|
446
|
-
|
|
447
|
-
// Validate the field using the validation function
|
|
448
|
-
if (!$validationFn($value)) {
|
|
449
|
-
Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
|
|
559
|
+
foreach ($schema as $field => $rule) {
|
|
560
|
+
$isRequired = $rule['required'] ?? false;
|
|
561
|
+
|
|
562
|
+
$has = is_object(Request::$params) && property_exists(Request::$params, $field);
|
|
563
|
+
if (!$has) {
|
|
564
|
+
if ($isRequired) {
|
|
565
|
+
Boom::badRequest("Missing {$field}")->toResponse();
|
|
566
|
+
return;
|
|
450
567
|
}
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
451
570
|
|
|
452
|
-
|
|
453
|
-
|
|
571
|
+
$raw = Request::$params->$field;
|
|
572
|
+
$out = null;
|
|
573
|
+
if (!($rule['validate'])($raw, $out)) {
|
|
574
|
+
$type = $rule['type'] ?? 'unknown';
|
|
575
|
+
Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
|
|
576
|
+
return;
|
|
454
577
|
}
|
|
578
|
+
$data[$field] = $out;
|
|
455
579
|
}
|
|
456
580
|
|
|
457
|
-
|
|
458
|
-
$new${modelName} = $prisma->${camelCaseModelName}->create([
|
|
459
|
-
'data' => $data
|
|
460
|
-
]);
|
|
581
|
+
$new${modelName} = $prisma->${camelCaseModelName}->create(['data' => $data]);
|
|
461
582
|
|
|
462
|
-
// Handle potential internal server error
|
|
463
583
|
if (!$new${modelName}) {
|
|
464
584
|
Boom::internal()->toResponse();
|
|
585
|
+
return;
|
|
465
586
|
}
|
|
466
587
|
|
|
467
|
-
// Return the newly created record in JSON format
|
|
468
588
|
echo json_encode($new${modelName});`;
|
|
469
589
|
|
|
470
590
|
writeFileSync(
|
|
@@ -478,6 +598,8 @@ echo json_encode($new${modelName});`;
|
|
|
478
598
|
mkdirSync(resolve(__dirname, `../${updateDir}`), { recursive: true });
|
|
479
599
|
const updateRoutePath = `${updateDir}/route.php`;
|
|
480
600
|
|
|
601
|
+
const updateSchema = generatePhpSchema(fieldsToCreateAndUpdate, true);
|
|
602
|
+
|
|
481
603
|
const updateRouteContent = `<?php
|
|
482
604
|
|
|
483
605
|
use Lib\\Prisma\\Classes\\Prisma;
|
|
@@ -487,65 +609,44 @@ use Lib\\Request;
|
|
|
487
609
|
|
|
488
610
|
$prisma = Prisma::getInstance();
|
|
489
611
|
$id = Request::$dynamicParams->id ?? null;
|
|
612
|
+
${idCheck}
|
|
490
613
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
Boom::badRequest("Invalid id")->toResponse();
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Define fields with their types, required status, and validation functions
|
|
497
|
-
$fieldsWithTypesAndStatus = [
|
|
498
|
-
${fieldsToCreateAndUpdate
|
|
499
|
-
.map(
|
|
500
|
-
(field) =>
|
|
501
|
-
` '${field.name}' => [
|
|
502
|
-
'type' => '${field.type.toLowerCase()}',
|
|
503
|
-
'required' => ${field.isRequired ? "true" : "false"},
|
|
504
|
-
'validate' => fn($value) => is_null($value) || $value === '' || Validator::${field.type.toLowerCase()}($value)
|
|
505
|
-
]`
|
|
506
|
-
)
|
|
507
|
-
.join(",\n")}
|
|
508
|
-
];
|
|
509
|
-
|
|
614
|
+
/** Partial update: nothing is required, but at least one field must be present */
|
|
615
|
+
$schema = ${updateSchema};
|
|
510
616
|
$data = [];
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
$
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
617
|
+
$any = false;
|
|
618
|
+
|
|
619
|
+
foreach ($schema as $field => $rule) {
|
|
620
|
+
$has = is_object(Request::$params) && property_exists(Request::$params, $field);
|
|
621
|
+
if (!$has) continue;
|
|
622
|
+
|
|
623
|
+
$raw = Request::$params->$field;
|
|
624
|
+
$out = null;
|
|
625
|
+
if (!($rule['validate'])($raw, $out)) {
|
|
626
|
+
$type = $rule['type'] ?? 'unknown';
|
|
627
|
+
Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
|
|
628
|
+
return;
|
|
519
629
|
}
|
|
630
|
+
$data[$field] = $out;
|
|
631
|
+
$any = true;
|
|
632
|
+
}
|
|
520
633
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// Validate the field using the validation function
|
|
526
|
-
if (!$validationFn($value)) {
|
|
527
|
-
Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Assign the validated value to the data array
|
|
531
|
-
$data[$field] = $value;
|
|
532
|
-
}
|
|
634
|
+
if (!$any) {
|
|
635
|
+
Boom::badRequest("No fields to update")->toResponse();
|
|
636
|
+
return;
|
|
533
637
|
}
|
|
534
638
|
|
|
535
|
-
// Update the record
|
|
536
639
|
$updated${modelName} = $prisma->${camelCaseModelName}->update([
|
|
537
640
|
'where' => ['${idFieldName}' => $id],
|
|
538
|
-
'data'
|
|
641
|
+
'data' => $data,
|
|
539
642
|
]);
|
|
540
643
|
|
|
541
|
-
// Handle potential internal server error
|
|
542
644
|
if (!$updated${modelName}) {
|
|
543
645
|
Boom::notFound()->toResponse();
|
|
646
|
+
return;
|
|
544
647
|
}
|
|
545
648
|
|
|
546
|
-
|
|
547
|
-
echo json_encode($updated${modelName});
|
|
548
|
-
`;
|
|
649
|
+
echo json_encode($updated${modelName});`;
|
|
549
650
|
|
|
550
651
|
writeFileSync(
|
|
551
652
|
resolve(__dirname, `../${updateRoutePath}`),
|
|
@@ -566,7 +667,7 @@ use Lib\\Request;
|
|
|
566
667
|
|
|
567
668
|
$prisma = Prisma::getInstance();
|
|
568
669
|
$id = Request::$dynamicParams->id ?? null;
|
|
569
|
-
${
|
|
670
|
+
${idCheck}
|
|
570
671
|
|
|
571
672
|
$deleted${modelName} = $prisma->${camelCaseModelName}->delete([
|
|
572
673
|
'where' => [
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
2
2
|
import { writeFileSync } from "fs";
|
|
3
|
-
import chokidar from "chokidar";
|
|
4
3
|
import browserSync, { BrowserSyncInstance } from "browser-sync";
|
|
5
4
|
import prismaPhpConfigJson from "../prisma-php.json";
|
|
6
5
|
import { generateFileListJson } from "./files-list.js";
|
|
@@ -14,75 +13,76 @@ import {
|
|
|
14
13
|
updateComponentImports,
|
|
15
14
|
} from "./class-imports";
|
|
16
15
|
import { checkComponentImports } from "./component-import-checker";
|
|
16
|
+
import { DebouncedWorker, createSrcWatcher, DEFAULT_AWF } from "./utils.js";
|
|
17
17
|
|
|
18
18
|
const { __dirname } = getFileMeta();
|
|
19
19
|
|
|
20
20
|
const bs: BrowserSyncInstance = browserSync.create();
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
22
|
+
// ---------- Watcher (whole ./src) ----------
|
|
23
|
+
const pipeline = new DebouncedWorker(
|
|
24
|
+
async () => {
|
|
25
|
+
await generateFileListJson();
|
|
26
|
+
await updateAllClassLogs();
|
|
27
|
+
await updateComponentImports();
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
await updateComponentImports();
|
|
29
|
+
// Scan all PHP files in the whole SRC tree
|
|
30
|
+
const phpFiles = await getAllPhpFiles(SRC_DIR);
|
|
31
|
+
for (const file of phpFiles) {
|
|
32
|
+
const rawFileImports = await analyzeImportsInFile(file);
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (const key in rawFileImports) {
|
|
47
|
-
if (typeof rawFileImports[key] === "string") {
|
|
48
|
-
fileImports[key] = [
|
|
49
|
-
{
|
|
50
|
-
className: key,
|
|
51
|
-
filePath: rawFileImports[key],
|
|
52
|
-
},
|
|
53
|
-
];
|
|
54
|
-
} else {
|
|
55
|
-
fileImports[key] = rawFileImports[key];
|
|
34
|
+
// Normalize to array-of-objects shape expected by the checker
|
|
35
|
+
const fileImports: Record<
|
|
36
|
+
string,
|
|
37
|
+
{ className: string; filePath: string; importer?: string }[]
|
|
38
|
+
> = {};
|
|
39
|
+
for (const key in rawFileImports) {
|
|
40
|
+
const v = rawFileImports[key];
|
|
41
|
+
fileImports[key] = Array.isArray(v)
|
|
42
|
+
? v
|
|
43
|
+
: [{ className: key, filePath: v }];
|
|
56
44
|
}
|
|
45
|
+
await checkComponentImports(file, fileImports);
|
|
57
46
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
},
|
|
48
|
+
350,
|
|
49
|
+
"bs-pipeline"
|
|
50
|
+
);
|
|
61
51
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
52
|
+
// watch the entire src; we don’t need an extension filter here
|
|
53
|
+
createSrcWatcher(join(SRC_DIR, "**", "*"), {
|
|
54
|
+
onEvent: (_ev, _abs, rel) => pipeline.schedule(rel),
|
|
55
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
56
|
+
logPrefix: "watch",
|
|
57
|
+
usePolling: true,
|
|
58
|
+
interval: 1000,
|
|
59
|
+
});
|
|
67
60
|
|
|
68
|
-
// BrowserSync
|
|
61
|
+
// ---------- BrowserSync ----------
|
|
69
62
|
bs.init(
|
|
70
63
|
{
|
|
64
|
+
/**
|
|
65
|
+
* Proxy your PHP app (from prisma-php.json).
|
|
66
|
+
* Use object form to enable WebSocket proxying.
|
|
67
|
+
*/
|
|
71
68
|
proxy: "http://localhost:3000",
|
|
69
|
+
|
|
72
70
|
middleware: [
|
|
73
|
-
(
|
|
71
|
+
(_req, res, next) => {
|
|
74
72
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
75
73
|
res.setHeader("Pragma", "no-cache");
|
|
76
74
|
res.setHeader("Expires", "0");
|
|
77
75
|
next();
|
|
78
76
|
},
|
|
77
|
+
|
|
79
78
|
createProxyMiddleware({
|
|
80
79
|
target: prismaPhpConfigJson.bsTarget,
|
|
81
80
|
changeOrigin: true,
|
|
82
81
|
pathRewrite: {},
|
|
83
82
|
}),
|
|
84
83
|
],
|
|
85
|
-
|
|
84
|
+
|
|
85
|
+
files: `${SRC_DIR}/**/*.*`, // still do file-level reloads as a safety net
|
|
86
86
|
notify: false,
|
|
87
87
|
open: false,
|
|
88
88
|
ghostMode: false,
|
|
@@ -98,24 +98,19 @@ bs.init(
|
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Construct the URLs dynamically
|
|
109
|
-
const urls = {
|
|
110
|
-
local: localUrl,
|
|
111
|
-
external: externalUrl,
|
|
112
|
-
ui: uiUrl,
|
|
113
|
-
uiExternal: uiExternalUrl,
|
|
101
|
+
// Write live URLs for other tooling
|
|
102
|
+
const urls = bsInstance.getOption("urls");
|
|
103
|
+
const out = {
|
|
104
|
+
local: urls.get("local"),
|
|
105
|
+
external: urls.get("external"),
|
|
106
|
+
ui: urls.get("ui"),
|
|
107
|
+
uiExternal: urls.get("ui-external"),
|
|
114
108
|
};
|
|
115
109
|
|
|
116
110
|
writeFileSync(
|
|
117
111
|
join(__dirname, "bs-config.json"),
|
|
118
|
-
JSON.stringify(
|
|
112
|
+
JSON.stringify(out, null, 2)
|
|
119
113
|
);
|
|
114
|
+
console.log("\n\x1b[90mPress Ctrl+C to stop.\x1b[0m\n");
|
|
120
115
|
}
|
|
121
116
|
);
|
|
@@ -5,6 +5,7 @@ import { getFileMeta } from "./utils.js";
|
|
|
5
5
|
import { promises as fsPromises } from "fs";
|
|
6
6
|
import { updateAllClassLogs } from "./class-log";
|
|
7
7
|
import { updateComponentImports } from "./class-imports";
|
|
8
|
+
import { generateFileListJson } from "./files-list";
|
|
8
9
|
|
|
9
10
|
const { __dirname } = getFileMeta();
|
|
10
11
|
|
|
@@ -147,6 +148,7 @@ export const dirsToDelete = [
|
|
|
147
148
|
join(__dirname, "..", ".pphp"),
|
|
148
149
|
];
|
|
149
150
|
|
|
151
|
+
await generateFileListJson();
|
|
150
152
|
await deleteFilesIfExist(filesToDelete);
|
|
151
153
|
await deleteDirectoriesIfExist(dirsToDelete);
|
|
152
154
|
await updateAllClassLogs();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import {
|
|
5
|
+
createRestartableProcess,
|
|
6
|
+
createSrcWatcher,
|
|
7
|
+
DebouncedWorker,
|
|
8
|
+
DEFAULT_AWF,
|
|
9
|
+
onExit,
|
|
10
|
+
} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
|
|
15
|
+
const phpPath = process.env.PHP_PATH || "php";
|
|
16
|
+
const serverScriptPath = join(
|
|
17
|
+
__dirname,
|
|
18
|
+
"..",
|
|
19
|
+
"src",
|
|
20
|
+
"Lib",
|
|
21
|
+
"MCP",
|
|
22
|
+
"mcp-server.php"
|
|
23
|
+
);
|
|
24
|
+
const watchRoot = join(__dirname, "..", "src");
|
|
25
|
+
|
|
26
|
+
// Restartable MCP server
|
|
27
|
+
const mcp = createRestartableProcess({
|
|
28
|
+
name: "MCP",
|
|
29
|
+
cmd: phpPath,
|
|
30
|
+
args: [serverScriptPath],
|
|
31
|
+
windowsKillTree: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
mcp.start();
|
|
35
|
+
|
|
36
|
+
// Debounced restarter
|
|
37
|
+
const restarter = new DebouncedWorker(
|
|
38
|
+
async () => {
|
|
39
|
+
await mcp.restart("file change");
|
|
40
|
+
},
|
|
41
|
+
250,
|
|
42
|
+
"mcp-restart"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Watch ./src for relevant changes
|
|
46
|
+
createSrcWatcher(watchRoot, {
|
|
47
|
+
exts: [".php", ".ts", ".js", ".json"],
|
|
48
|
+
onEvent: (ev, _abs, rel) => restarter.schedule(`${ev}: ${rel}`),
|
|
49
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
50
|
+
logPrefix: "MCP watch",
|
|
51
|
+
usePolling: true,
|
|
52
|
+
interval: 1000,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Graceful shutdown
|
|
56
|
+
onExit(async () => {
|
|
57
|
+
await mcp.stop();
|
|
58
|
+
});
|