create-prisma-php-app 1.26.1 → 1.26.3
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 +80 -23
- package/dist/index.js +1 -1
- package/dist/settings/auto-swagger-docs.ts +409 -214
- package/dist/settings/bs-config.ts +2 -2
- package/dist/settings/prisma-schema-config.json +13 -4
- package/dist/settings/project-name.ts +10 -7
- package/dist/settings/swagger-config.ts +5 -4
- package/dist/src/Lib/Auth/Auth.php +3 -3
- package/dist/src/Lib/PrismaPHPSettings.php +69 -15
- package/dist/src/Lib/Request.php +32 -4
- package/package.json +1 -1
package/dist/bootstrap.php
CHANGED
|
@@ -381,7 +381,11 @@ function checkForDuplicateRoutes()
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
if (!empty($errorMessages)) {
|
|
384
|
-
|
|
384
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
385
|
+
$errorMessageString = implode("\n", $errorMessages);
|
|
386
|
+
} else {
|
|
387
|
+
$errorMessageString = implode("<br>", $errorMessages);
|
|
388
|
+
}
|
|
385
389
|
modifyOutputLayoutForError($errorMessageString);
|
|
386
390
|
}
|
|
387
391
|
}
|
|
@@ -571,18 +575,36 @@ function modifyOutputLayoutForError($contentToAdd)
|
|
|
571
575
|
|
|
572
576
|
$errorContent = $contentToAdd;
|
|
573
577
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
578
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
579
|
+
header('Content-Type: application/json');
|
|
580
|
+
echo json_encode([
|
|
581
|
+
'success' => false,
|
|
582
|
+
'error' => $errorContent
|
|
583
|
+
]);
|
|
584
|
+
http_response_code(403);
|
|
581
585
|
} else {
|
|
582
|
-
|
|
586
|
+
$layoutFile = APP_PATH . '/layout.php';
|
|
587
|
+
if (file_exists($layoutFile)) {
|
|
588
|
+
|
|
589
|
+
ob_start();
|
|
590
|
+
require_once $errorFile;
|
|
591
|
+
MainLayout::$children = ob_get_clean();
|
|
592
|
+
require_once $layoutFile;
|
|
593
|
+
} else {
|
|
594
|
+
echo $errorContent;
|
|
595
|
+
}
|
|
583
596
|
}
|
|
584
597
|
} else {
|
|
585
|
-
|
|
598
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
599
|
+
header('Content-Type: application/json');
|
|
600
|
+
echo json_encode([
|
|
601
|
+
'success' => false,
|
|
602
|
+
'error' => $contentToAdd
|
|
603
|
+
]);
|
|
604
|
+
http_response_code(403);
|
|
605
|
+
} else {
|
|
606
|
+
echo $contentToAdd;
|
|
607
|
+
}
|
|
586
608
|
}
|
|
587
609
|
exit;
|
|
588
610
|
}
|
|
@@ -646,21 +668,34 @@ function authenticateUserToken()
|
|
|
646
668
|
}
|
|
647
669
|
|
|
648
670
|
set_exception_handler(function ($exception) {
|
|
649
|
-
|
|
671
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
672
|
+
$errorContent = "Exception: " . $exception->getMessage();
|
|
673
|
+
} else {
|
|
674
|
+
$errorContent = "<div class='error'>Exception: " . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . "</div>";
|
|
675
|
+
}
|
|
650
676
|
modifyOutputLayoutForError($errorContent);
|
|
651
677
|
});
|
|
652
678
|
|
|
653
679
|
register_shutdown_function(function () {
|
|
654
680
|
$error = error_get_last();
|
|
655
681
|
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR])) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
"
|
|
659
|
-
|
|
682
|
+
|
|
683
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
684
|
+
$errorContent = "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line'];
|
|
685
|
+
} else {
|
|
686
|
+
$errorContent = "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
|
|
687
|
+
" in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
|
|
688
|
+
" on line " . $error['line'] . "</div>";
|
|
689
|
+
}
|
|
660
690
|
modifyOutputLayoutForError($errorContent);
|
|
661
691
|
}
|
|
662
692
|
});
|
|
663
693
|
|
|
694
|
+
function isAjaxOrXFileRequestOrRouteFile(): bool
|
|
695
|
+
{
|
|
696
|
+
return Request::$isAjax || Request::$isXFileRequest || Request::$fileToInclude === 'route.php';
|
|
697
|
+
}
|
|
698
|
+
|
|
664
699
|
try {
|
|
665
700
|
$_determineContentToInclude = determineContentToInclude();
|
|
666
701
|
$_contentToInclude = $_determineContentToInclude['path'] ?? '';
|
|
@@ -800,24 +835,42 @@ try {
|
|
|
800
835
|
}
|
|
801
836
|
} else {
|
|
802
837
|
if ($_isContentIncluded) {
|
|
803
|
-
|
|
838
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
839
|
+
$_errorDetails = "The layout file does not contain <?php echo MainLayout::\$childLayoutChildren; ?> or <?= MainLayout::\$childLayoutChildren ?><br><strong>$layoutPath</strong>";
|
|
840
|
+
} else {
|
|
841
|
+
$_errorDetails = "<div class='error'>The parent layout file does not contain <?php echo MainLayout::\$children; ?> Or <?= MainLayout::\$children ?><br>" . "<strong>$_parentLayoutPath</strong></div>";
|
|
842
|
+
}
|
|
843
|
+
modifyOutputLayoutForError($_errorDetails);
|
|
804
844
|
} else {
|
|
805
|
-
|
|
845
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
846
|
+
$_errorDetails = "The layout file does not contain <?php echo MainLayout::\$childLayoutChildren; ?> or <?= MainLayout::\$childLayoutChildren ?><br><strong>$layoutPath</strong>";
|
|
847
|
+
} else {
|
|
848
|
+
$_errorDetails = "<div class='error'>The layout file does not contain <?php echo MainLayout::\$childLayoutChildren; ?> or <?= MainLayout::\$childLayoutChildren ?><br><strong>$layoutPath</strong></div>";
|
|
849
|
+
}
|
|
806
850
|
modifyOutputLayoutForError($_errorDetails);
|
|
807
851
|
}
|
|
808
852
|
}
|
|
809
853
|
} catch (Throwable $e) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
854
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
855
|
+
$_errorDetails = "Unhandled Exception: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
|
|
856
|
+
} else {
|
|
857
|
+
$_errorDetails = "Unhandled Exception: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
|
858
|
+
$_errorDetails .= "<br>File: " . htmlspecialchars($e->getFile(), ENT_QUOTES, 'UTF-8');
|
|
859
|
+
$_errorDetails .= "<br>Line: " . htmlspecialchars($e->getLine(), ENT_QUOTES, 'UTF-8');
|
|
860
|
+
$_errorDetails = "<div class='error'>$_errorDetails</div>";
|
|
861
|
+
}
|
|
814
862
|
modifyOutputLayoutForError($_errorDetails);
|
|
815
863
|
}
|
|
816
864
|
|
|
817
865
|
(function () {
|
|
818
866
|
$lastErrorCapture = error_get_last();
|
|
819
867
|
if ($lastErrorCapture !== null) {
|
|
820
|
-
|
|
868
|
+
|
|
869
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
870
|
+
$errorContent = "Error: " . $lastErrorCapture['message'] . " in " . $lastErrorCapture['file'] . " on line " . $lastErrorCapture['line'];
|
|
871
|
+
} else {
|
|
872
|
+
$errorContent = "<div class='error'>Error: " . $lastErrorCapture['message'] . " in " . $lastErrorCapture['file'] . " on line " . $lastErrorCapture['line'] . "</div>";
|
|
873
|
+
}
|
|
821
874
|
modifyOutputLayoutForError($errorContent);
|
|
822
875
|
}
|
|
823
876
|
})();
|
|
@@ -829,7 +882,11 @@ set_error_handler(function ($severity, $message, $file, $line) {
|
|
|
829
882
|
}
|
|
830
883
|
|
|
831
884
|
// Capture the specific severity types, including warnings (E_WARNING)
|
|
832
|
-
|
|
885
|
+
if (isAjaxOrXFileRequestOrRouteFile()) {
|
|
886
|
+
$errorContent = "Error: {$severity} - {$message} in {$file} on line {$line}";
|
|
887
|
+
} else {
|
|
888
|
+
$errorContent = "<div class='error'>Error: {$message} in {$file} on line {$line}</div>";
|
|
889
|
+
}
|
|
833
890
|
|
|
834
891
|
// If needed, log it or output immediately based on severity
|
|
835
892
|
if ($severity === E_WARNING || $severity === E_NOTICE) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{execSync}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";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"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const i=c.replace(/(?<!:)(\/\/+)/g,"/"),o=n.replace(/\/\/+/g,"/");return{bsTarget:`${i}/`,bsPathRewrite:{"^/":`/${o.startsWith("/")?o.substring(1):o}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let i={...n.scripts};i.browserSync="tsx settings/bs-config.ts",i.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,n.scripts=i,n.type="module",s.prisma&&(n.prisma={seed:"tsx prisma/seed.ts"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e,s){const t=path.join(e,"composer.json");if(checkExcludeFiles(t))return;let n;if(fs.existsSync(t)){{const e=fs.readFileSync(t,"utf8");n=JSON.parse(e)}s.websocket&&(n.require={...n.require,"cboden/ratchet":"^0.4.4"}),s.prisma&&(n.require={...n.require,"ramsey/uuid":"5.x-dev","hidehalo/nanoid-php":"1.x-dev"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}async function createUpdateGitignoreFile(e,s){const t=path.join(e,".gitignore");if(checkExcludeFiles(t))return;let n="";s.forEach((e=>{n.includes(e)||(n+=`\n${e}`)})),n=n.trimStart(),fs.writeFileSync(t,n)}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(!t.prisma&&n.includes("src\\lib\\prisma"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")))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-sdk.ts")||s.includes("prisma-schema-config.json")||s.includes("prisma-schema.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function createOrUpdateTailwindConfig(e){const s=path.join(e,"tailwind.config.js");if(checkExcludeFiles(s))return;let t=fs.readFileSync(s,"utf8");const n=["./src/**/*.{html,js,php}"].map((e=>` "${e}"`)).join(",\n");t=t.replace(/content: \[\],/g,`content: [\n${n}\n],`),fs.writeFileSync(s,t,{flag:"w"})}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,"export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\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"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet">'),n+='\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet"> ${n}`:n);const i=c.length>0?"\n":"";e=e.replace("</head>",`${c}${i} \x3c!-- Dynamic Head --\x3e\n <?= MainLayout::outputHeadScripts() ?>\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(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:"/../composer.json",dest:"/composer.json"},{src:"/tsconfig.json",dest:"/tsconfig.json"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"},{src:"/tailwind.config.js",dest:"/tailwind.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.prisma&&n.push({src:"/prisma",dest:"/prisma"}),s.docker&&n.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 n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const i=fs.readFileSync(n,"utf8");fs.writeFileSync(c,i,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e,s),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&(createOrUpdateTailwindConfig(e),modifyPostcssConfig(e)),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c='# Prisma PHP Auth Secret Key For development only - Change this in production\nAUTH_SECRET=uxsjXVPHN038DEYls2Kw0QUgBcXKUyrjv416nIFWPY4= \n \n# PHPMailer\n# SMTP_HOST=smtp.gmail.com or your SMTP host\n# SMTP_USERNAME=john.doe@gmail.com or your SMTP username\n# SMTP_PASSWORD=123456\n# SMTP_PORT=587 for TLS, 465 for SSL or your SMTP port\n# SMTP_ENCRYPTION=ssl or tls\n# MAIL_FROM=john.doe@gmail.com\n# MAIL_FROM_NAME="John Doe"\n\n# SHOW ERRORS - Set to true to show errors in the browser for development only - Change this in production to false\nSHOW_ERRORS=true\n\n# APP TIMEZONE - Set your application timezone - Default is "UTC"\nAPP_TIMEZONE="UTC"\n\n# APP ENV - Set your application environment - Default is "development" - Change this in production to "production"\nAPP_ENV=development';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${c}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,c);await createUpdateGitignoreFile(e,["vendor",".env","node_modules"])}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||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=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly||e.backendOnly?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!0,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);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),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[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]:null}catch(e){return null}}
|
|
2
|
+
import{execSync}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";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"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const i=c.replace(/(?<!:)(\/\/+)/g,"/"),o=n.replace(/\/\/+/g,"/");return{bsTarget:`${i}/`,bsPathRewrite:{"^/":`/${o.startsWith("/")?o.substring(1):o}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let i={...n.scripts};i.browserSync="tsx settings/bs-config.ts",i.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,n.scripts=i,n.type="module",s.prisma&&(n.prisma={seed:"tsx prisma/seed.ts"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e,s){const t=path.join(e,"composer.json");if(checkExcludeFiles(t))return;let n;if(fs.existsSync(t)){{const e=fs.readFileSync(t,"utf8");n=JSON.parse(e)}s.websocket&&(n.require={...n.require,"cboden/ratchet":"^0.4.4"}),s.prisma&&(n.require={...n.require,"ramsey/uuid":"5.x-dev","hidehalo/nanoid-php":"1.x-dev"}),fs.writeFileSync(t,JSON.stringify(n,null,2))}}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}async function createUpdateGitignoreFile(e,s){const t=path.join(e,".gitignore");if(checkExcludeFiles(t))return;let n="";s.forEach((e=>{n.includes(e)||(n+=`\n${e}`)})),n=n.trimStart(),fs.writeFileSync(t,n)}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(!t.prisma&&n.includes("src\\lib\\prisma"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")))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-sdk.ts")||s.includes("prisma-schema-config.json")||s.includes("prisma-schema.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function createOrUpdateTailwindConfig(e){const s=path.join(e,"tailwind.config.js");if(checkExcludeFiles(s))return;let t=fs.readFileSync(s,"utf8");const n=["./src/**/*.{html,js,php}"].map((e=>` "${e}"`)).join(",\n");t=t.replace(/content: \[\],/g,`content: [\n${n}\n],`),fs.writeFileSync(s,t,{flag:"w"})}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,"export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\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"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet">'),n+='\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet"> ${n}`:n);const i=c.length>0?"\n":"";e=e.replace("</head>",`${c}${i} \x3c!-- Dynamic Head --\x3e\n <?= MainLayout::outputHeadScripts() ?>\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(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:"/../composer.json",dest:"/composer.json"},{src:"/tsconfig.json",dest:"/tsconfig.json"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"},{src:"/tailwind.config.js",dest:"/tailwind.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.prisma&&n.push({src:"/prisma",dest:"/prisma"}),s.docker&&n.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 n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const i=fs.readFileSync(n,"utf8");fs.writeFileSync(c,i,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e,s),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&(createOrUpdateTailwindConfig(e),modifyPostcssConfig(e)),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c='# Prisma PHP Auth Secret Key For development only - Change this in production\nAUTH_SECRET=uxsjXVPHN038DEYls2Kw0QUgBcXKUyrjv416nIFWPY4= \n \n# PHPMailer\n# SMTP_HOST=smtp.gmail.com or your SMTP host\n# SMTP_USERNAME=john.doe@gmail.com or your SMTP username\n# SMTP_PASSWORD=123456\n# SMTP_PORT=587 for TLS, 465 for SSL or your SMTP port\n# SMTP_ENCRYPTION=ssl or tls\n# MAIL_FROM=john.doe@gmail.com\n# MAIL_FROM_NAME="John Doe"\n\n# SHOW ERRORS - Set to true to show errors in the browser for development only - Change this in production to false\nSHOW_ERRORS=true\n\n# APP TIMEZONE - Set your application timezone - Default is "UTC"\nAPP_TIMEZONE="UTC"\n\n# APP ENV - Set your application environment - Default is "development" - Change this in production to "production"\nAPP_ENV=development';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${c}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,c);await createUpdateGitignoreFile(e,["vendor",".env","node_modules"])}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||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=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly||e.backendOnly?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!0,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);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),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[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]:null}catch(e){return null}}
|
|
3
3
|
/**
|
|
4
4
|
* Install dependencies in the specified directory.
|
|
5
5
|
* @param {string} baseDir - The base directory where to install the dependencies.
|
|
@@ -7,12 +7,145 @@ import { swaggerConfig } from "./swagger-config.js";
|
|
|
7
7
|
import { getFileMeta } from "./utils.js";
|
|
8
8
|
import prismaSchemaConfigJson from "./prisma-schema-config.json";
|
|
9
9
|
import prompts from "prompts";
|
|
10
|
+
import { exit } from "process";
|
|
10
11
|
|
|
11
12
|
const { __dirname } = getFileMeta();
|
|
12
13
|
const prismaSchemaJsonPath = resolve(__dirname, "./prisma-schema.json");
|
|
13
14
|
|
|
15
|
+
type PrismaSchemaConfig = {
|
|
16
|
+
swaggerDocsDir: string;
|
|
17
|
+
skipDefaultName: string[];
|
|
18
|
+
skipByPropertyValue: Record<string, boolean>;
|
|
19
|
+
skipFields: string[];
|
|
20
|
+
generateEndpoints: boolean;
|
|
21
|
+
generatePhpClasses: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Field = {
|
|
25
|
+
name: string;
|
|
26
|
+
kind: string;
|
|
27
|
+
isList: boolean;
|
|
28
|
+
isRequired: boolean;
|
|
29
|
+
isUnique: boolean;
|
|
30
|
+
isId: boolean;
|
|
31
|
+
isReadOnly: boolean;
|
|
32
|
+
hasDefaultValue: boolean;
|
|
33
|
+
type: string;
|
|
34
|
+
isGenerated: boolean;
|
|
35
|
+
isUpdatedAt: boolean;
|
|
36
|
+
default?: {
|
|
37
|
+
name: string;
|
|
38
|
+
args: any[];
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function shouldSkipField(field: Field): boolean {
|
|
43
|
+
const config: PrismaSchemaConfig = prismaSchemaConfigJson;
|
|
44
|
+
|
|
45
|
+
if (field.kind === "object") {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Skip fields that are explicitly marked to be skipped by name
|
|
50
|
+
if (config.skipFields && config.skipFields.includes(field.name)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Skip fields based on specific property values defined in skipByPropertyValue
|
|
55
|
+
for (const [property, value] of Object.entries(config.skipByPropertyValue)) {
|
|
56
|
+
if ((field as any)[property] === value) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Skip ID fields with auto-creation during creation
|
|
62
|
+
if (config.skipDefaultName.includes(field.default?.name || "")) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Function to determine an appropriate example based on the field type for Prisma ORM
|
|
70
|
+
function getExampleValue(field: Field): any {
|
|
71
|
+
const fieldType = field.type.toLowerCase();
|
|
72
|
+
|
|
73
|
+
if (field.isId) {
|
|
74
|
+
// Provide examples based on common ID types
|
|
75
|
+
if (field.hasDefaultValue) {
|
|
76
|
+
switch (field.default?.name.toLowerCase()) {
|
|
77
|
+
case "uuid(4)":
|
|
78
|
+
return `"123e4567-e89b-12d3-a456-426614174000"`; // Example for UUID IDs
|
|
79
|
+
case "cuid":
|
|
80
|
+
return `"cjrscj5d40002s6s0b6nq9jfg"`; // Example for CUID IDs
|
|
81
|
+
case "autoincrement":
|
|
82
|
+
return 1; // Example for auto-increment IDs
|
|
83
|
+
default:
|
|
84
|
+
return `"${field.name}"`; // Default example for unknown ID types
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
switch (fieldType) {
|
|
88
|
+
case "int":
|
|
89
|
+
case "bigint":
|
|
90
|
+
return 123; // Example for integer and BigInt IDs
|
|
91
|
+
default:
|
|
92
|
+
return `"${field.name}"`; // Default example for unknown ID types
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Example values for other field types
|
|
98
|
+
switch (fieldType) {
|
|
99
|
+
case "int":
|
|
100
|
+
case "bigint":
|
|
101
|
+
return 123; // Example for integer and BigInt types
|
|
102
|
+
case "float":
|
|
103
|
+
case "decimal":
|
|
104
|
+
return 123.45; // Example for floating-point types
|
|
105
|
+
case "boolean":
|
|
106
|
+
return true; // Example for boolean types
|
|
107
|
+
case "string":
|
|
108
|
+
return `"${field.name}"`; // Example for string types
|
|
109
|
+
case "datetime":
|
|
110
|
+
return `"2024-01-01T00:00:00Z"`; // Example for date/time types
|
|
111
|
+
case "json":
|
|
112
|
+
return `{"key": "value"}`; // Example for JSON type
|
|
113
|
+
case "uuid":
|
|
114
|
+
return `"123e4567-e89b-12d3-a456-426614174000"`; // Example for UUID type
|
|
115
|
+
case "cuid":
|
|
116
|
+
return `"cjrscj5d40002s6s0b6nq9jfg"`; // Example for CUID type
|
|
117
|
+
default:
|
|
118
|
+
return `"${field.name}"`; // Default example for unrecognized types
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convert a Prisma field type to a Swagger-supported type.
|
|
124
|
+
* @param prismaType The type of the field as defined in the Prisma schema
|
|
125
|
+
* @returns A Swagger-compatible type
|
|
126
|
+
*/
|
|
127
|
+
function convertPrismaTypeToSwaggerType(prismaType: string): string {
|
|
128
|
+
// Map Prisma types to Swagger-compatible types
|
|
129
|
+
const typeMapping: Record<string, string> = {
|
|
130
|
+
String: "string",
|
|
131
|
+
Int: "integer",
|
|
132
|
+
BigInt: "integer",
|
|
133
|
+
Float: "number",
|
|
134
|
+
Decimal: "number",
|
|
135
|
+
Boolean: "boolean",
|
|
136
|
+
DateTime: "string", // For Swagger, we use "string" with format date-time
|
|
137
|
+
Json: "object",
|
|
138
|
+
UUID: "string",
|
|
139
|
+
CUID: "string",
|
|
140
|
+
Bytes: "string", // Can be represented as base64 strings in Swagger
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Default to "string" if the type is not found in the mapping
|
|
144
|
+
return typeMapping[prismaType] || "string";
|
|
145
|
+
}
|
|
146
|
+
|
|
14
147
|
// Function to generate properties for Swagger annotations
|
|
15
|
-
function generateProperties(fields:
|
|
148
|
+
function generateProperties(fields: Field[]): {
|
|
16
149
|
properties: string;
|
|
17
150
|
allProperties: string;
|
|
18
151
|
} {
|
|
@@ -24,24 +157,21 @@ function generateProperties(fields: any[]): {
|
|
|
24
157
|
return;
|
|
25
158
|
}
|
|
26
159
|
|
|
27
|
-
const example =
|
|
160
|
+
const example = getExampleValue(field);
|
|
161
|
+
const fieldType = convertPrismaTypeToSwaggerType(field.type); // Convert Prisma type to Swagger type
|
|
28
162
|
allProperties += `
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
163
|
+
* ${field.name}:
|
|
164
|
+
* type: ${fieldType}
|
|
165
|
+
* example: ${example}`;
|
|
32
166
|
|
|
33
|
-
if (
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (prismaSchemaConfigJson.skipDefaultName.includes(field.default?.name)) {
|
|
167
|
+
if (shouldSkipField(field)) {
|
|
38
168
|
return;
|
|
39
169
|
}
|
|
40
170
|
|
|
41
171
|
properties += `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
172
|
+
* ${field.name}:
|
|
173
|
+
* type: ${fieldType}
|
|
174
|
+
* example: ${example}`;
|
|
45
175
|
});
|
|
46
176
|
|
|
47
177
|
return { properties, allProperties };
|
|
@@ -54,170 +184,143 @@ function toKebabCase(str: string): string {
|
|
|
54
184
|
.toLowerCase();
|
|
55
185
|
}
|
|
56
186
|
|
|
187
|
+
// Function to find the ID field from the model
|
|
188
|
+
function getIdField(fields: Field[]): Field | undefined {
|
|
189
|
+
return fields.find((field) => field.isId);
|
|
190
|
+
}
|
|
191
|
+
|
|
57
192
|
// Function to generate Swagger annotation for a CRUD operation
|
|
58
|
-
function generateSwaggerAnnotation(modelName: string, fields:
|
|
193
|
+
function generateSwaggerAnnotation(modelName: string, fields: Field[]): string {
|
|
194
|
+
// Extract the ID field dynamically
|
|
195
|
+
const idField = getIdField(fields);
|
|
196
|
+
if (!idField) {
|
|
197
|
+
throw new Error(`No ID field found for model: ${modelName}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const idFieldName = idField.name;
|
|
201
|
+
const idFieldType = convertPrismaTypeToSwaggerType(idField.type); // Convert Prisma type to Swagger type
|
|
59
202
|
const { properties, allProperties } = generateProperties(fields);
|
|
60
203
|
const kebabCaseModelName = toKebabCase(modelName);
|
|
61
204
|
|
|
62
205
|
return `/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Function to generate dynamic validation rules and request payloads
|
|
185
|
-
function generateValidationAndPayload(fields: any[]) {
|
|
186
|
-
let validations = "";
|
|
187
|
-
let payload = "";
|
|
188
|
-
let variableAssignments = "";
|
|
189
|
-
|
|
190
|
-
fields.forEach((field) => {
|
|
191
|
-
if (field.kind === "object") return; // Skip relations for now
|
|
192
|
-
|
|
193
|
-
// Skip fields that are explicitly marked to be skipped
|
|
194
|
-
if (prismaSchemaConfigJson.skipFields.includes(field.name)) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Skip ID fields with auto-creation during creation
|
|
199
|
-
if (prismaSchemaConfigJson.skipDefaultName.includes(field.default?.name)) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Define variable assignments
|
|
204
|
-
const variableName = field.name;
|
|
205
|
-
variableAssignments += `$${variableName} = Request::$params->${variableName} ?? null;\n`;
|
|
206
|
-
|
|
207
|
-
// Dynamic validation for required fields (excluding skipped fields)
|
|
208
|
-
if (field.isRequired) {
|
|
209
|
-
const fieldType = field.type.toLowerCase();
|
|
210
|
-
validations += `
|
|
211
|
-
if (!Validator::${fieldType}($${variableName})) {
|
|
212
|
-
Boom::badRequest("Invalid ${variableName}")->toResponse();
|
|
213
|
-
}`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Prepare payload dynamically
|
|
217
|
-
payload += `'${variableName}' => $${variableName},\n `;
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
return { validations, payload, variableAssignments };
|
|
206
|
+
* @swagger
|
|
207
|
+
* tags:
|
|
208
|
+
* name: ${modelName}
|
|
209
|
+
* description: ${modelName} management API
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @swagger
|
|
214
|
+
* /${kebabCaseModelName}:
|
|
215
|
+
* get:
|
|
216
|
+
* summary: Retrieve a list of ${modelName}
|
|
217
|
+
* tags:
|
|
218
|
+
* - ${modelName}
|
|
219
|
+
* responses:
|
|
220
|
+
* 200:
|
|
221
|
+
* description: A list of ${modelName}
|
|
222
|
+
* content:
|
|
223
|
+
* application/json:
|
|
224
|
+
* schema:
|
|
225
|
+
* type: array
|
|
226
|
+
* items:
|
|
227
|
+
* type: object
|
|
228
|
+
* properties:${allProperties}
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @swagger
|
|
233
|
+
* /${kebabCaseModelName}/{${idFieldName}}:
|
|
234
|
+
* get:
|
|
235
|
+
* summary: Retrieve a single ${modelName} by ${idFieldName}
|
|
236
|
+
* tags:
|
|
237
|
+
* - ${modelName}
|
|
238
|
+
* parameters:
|
|
239
|
+
* - in: path
|
|
240
|
+
* name: ${idFieldName}
|
|
241
|
+
* required: true
|
|
242
|
+
* description: The ${modelName} ${idFieldName}
|
|
243
|
+
* schema:
|
|
244
|
+
* type: ${idFieldType}
|
|
245
|
+
* responses:
|
|
246
|
+
* 200:
|
|
247
|
+
* description: A single ${modelName} object
|
|
248
|
+
* content:
|
|
249
|
+
* application/json:
|
|
250
|
+
* schema:
|
|
251
|
+
* type: object
|
|
252
|
+
* properties:${allProperties}
|
|
253
|
+
* 404:
|
|
254
|
+
* description: ${modelName} not found
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @swagger
|
|
259
|
+
* /${kebabCaseModelName}/create:
|
|
260
|
+
* post:
|
|
261
|
+
* summary: Create a new ${modelName}
|
|
262
|
+
* tags:
|
|
263
|
+
* - ${modelName}
|
|
264
|
+
* requestBody:
|
|
265
|
+
* required: true
|
|
266
|
+
* content:
|
|
267
|
+
* application/json:
|
|
268
|
+
* schema:
|
|
269
|
+
* type: object
|
|
270
|
+
* properties:${properties}
|
|
271
|
+
* responses:
|
|
272
|
+
* 201:
|
|
273
|
+
* description: ${modelName} created successfully.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @swagger
|
|
278
|
+
* /${kebabCaseModelName}/update/{${idFieldName}}:
|
|
279
|
+
* put:
|
|
280
|
+
* summary: Update a ${modelName} by ${idFieldName}
|
|
281
|
+
* tags:
|
|
282
|
+
* - ${modelName}
|
|
283
|
+
* parameters:
|
|
284
|
+
* - in: path
|
|
285
|
+
* name: ${idFieldName}
|
|
286
|
+
* required: true
|
|
287
|
+
* description: The ${modelName} ${idFieldName}
|
|
288
|
+
* schema:
|
|
289
|
+
* type: ${idFieldType}
|
|
290
|
+
* requestBody:
|
|
291
|
+
* required: true
|
|
292
|
+
* content:
|
|
293
|
+
* application/json:
|
|
294
|
+
* schema:
|
|
295
|
+
* type: object
|
|
296
|
+
* properties:${properties}
|
|
297
|
+
* responses:
|
|
298
|
+
* 200:
|
|
299
|
+
* description: ${modelName} updated successfully.
|
|
300
|
+
* 404:
|
|
301
|
+
* description: ${modelName} not found
|
|
302
|
+
*/
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @swagger
|
|
306
|
+
* /${kebabCaseModelName}/delete/{${idFieldName}}:
|
|
307
|
+
* delete:
|
|
308
|
+
* summary: Delete a ${modelName} by ${idFieldName}
|
|
309
|
+
* tags:
|
|
310
|
+
* - ${modelName}
|
|
311
|
+
* parameters:
|
|
312
|
+
* - in: path
|
|
313
|
+
* name: ${idFieldName}
|
|
314
|
+
* required: true
|
|
315
|
+
* description: The ${modelName} ${idFieldName}
|
|
316
|
+
* schema:
|
|
317
|
+
* type: ${idFieldType}
|
|
318
|
+
* responses:
|
|
319
|
+
* 204:
|
|
320
|
+
* description: ${modelName} successfully deleted
|
|
321
|
+
* 404:
|
|
322
|
+
* description: ${modelName} not found
|
|
323
|
+
*/`;
|
|
221
324
|
}
|
|
222
325
|
|
|
223
326
|
// Function to generate dynamic ID validation logic for update and find-by-ID routes
|
|
@@ -241,6 +344,10 @@ function generateEndpoints(modelName: string, fields: any[]): void {
|
|
|
241
344
|
modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
242
345
|
const baseDir = `src/app/${kebabCasedModelName}`;
|
|
243
346
|
const idField = fields.find((field) => field.isId);
|
|
347
|
+
const fieldsToCreateAndUpdate = fields.filter(
|
|
348
|
+
(field) => shouldSkipField(field) === false
|
|
349
|
+
);
|
|
350
|
+
const idFieldName = idField.name;
|
|
244
351
|
const baseDirPath = resolve(__dirname, `../${baseDir}`);
|
|
245
352
|
|
|
246
353
|
mkdirSync(baseDirPath, { recursive: true });
|
|
@@ -279,7 +386,7 @@ ${idValidationLogic}
|
|
|
279
386
|
|
|
280
387
|
$${camelCaseModelName} = $prisma->${camelCaseModelName}->findUnique([
|
|
281
388
|
'where' => [
|
|
282
|
-
'
|
|
389
|
+
'${idFieldName}' => $id
|
|
283
390
|
]
|
|
284
391
|
]);
|
|
285
392
|
|
|
@@ -295,15 +402,10 @@ echo json_encode($${camelCaseModelName});`;
|
|
|
295
402
|
);
|
|
296
403
|
|
|
297
404
|
// Endpoint: POST /{kebabCasedModelName}/create
|
|
298
|
-
const {
|
|
299
|
-
validations: createValidations,
|
|
300
|
-
payload: createPayload,
|
|
301
|
-
variableAssignments,
|
|
302
|
-
} = generateValidationAndPayload(fields);
|
|
303
|
-
|
|
304
405
|
const createDir = `${baseDir}/create`;
|
|
305
406
|
mkdirSync(resolve(__dirname, `../${createDir}`), { recursive: true });
|
|
306
407
|
const createRoutePath = `${createDir}/route.php`;
|
|
408
|
+
|
|
307
409
|
const createRouteContent = `<?php
|
|
308
410
|
|
|
309
411
|
use Lib\\Prisma\\Classes\\Prisma;
|
|
@@ -312,26 +414,57 @@ use Lib\\Headers\\Boom;
|
|
|
312
414
|
use Lib\\Request;
|
|
313
415
|
|
|
314
416
|
$prisma = Prisma::getInstance();
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
417
|
+
|
|
418
|
+
// Define fields with their types, required status, and validation functions
|
|
419
|
+
$fieldsWithTypesAndStatus = [
|
|
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
|
+
];
|
|
431
|
+
|
|
432
|
+
$data = [];
|
|
433
|
+
foreach ($fieldsWithTypesAndStatus as $field => $details) {
|
|
434
|
+
$isRequired = $details['required'];
|
|
435
|
+
$type = $details['type'];
|
|
436
|
+
$validationFn = $details['validate'];
|
|
437
|
+
|
|
438
|
+
// Check if the field is required and missing in the request
|
|
439
|
+
if ($isRequired && !isset(Request::$params->$field)) {
|
|
440
|
+
Boom::badRequest("Missing {$field}")->toResponse();
|
|
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();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Assign the validated value to the data array
|
|
453
|
+
$data[$field] = $value;
|
|
454
|
+
}
|
|
324
455
|
}
|
|
325
456
|
|
|
457
|
+
// Create the new record using the Prisma instance
|
|
326
458
|
$new${modelName} = $prisma->${camelCaseModelName}->create([
|
|
327
|
-
'data' =>
|
|
328
|
-
${createPayload}
|
|
329
|
-
]
|
|
459
|
+
'data' => $data
|
|
330
460
|
]);
|
|
331
461
|
|
|
462
|
+
// Handle potential internal server error
|
|
332
463
|
if (!$new${modelName}) {
|
|
333
464
|
Boom::internal()->toResponse();
|
|
334
465
|
}
|
|
466
|
+
|
|
467
|
+
// Return the newly created record in JSON format
|
|
335
468
|
echo json_encode($new${modelName});`;
|
|
336
469
|
|
|
337
470
|
writeFileSync(
|
|
@@ -341,12 +474,10 @@ echo json_encode($new${modelName});`;
|
|
|
341
474
|
);
|
|
342
475
|
|
|
343
476
|
// Endpoint: PUT /{kebabCasedModelName}/update/{id}
|
|
344
|
-
const { validations: updateValidations, payload: updatePayload } =
|
|
345
|
-
generateValidationAndPayload(fields);
|
|
346
|
-
|
|
347
477
|
const updateDir = `${baseDir}/update/[id]`;
|
|
348
478
|
mkdirSync(resolve(__dirname, `../${updateDir}`), { recursive: true });
|
|
349
479
|
const updateRoutePath = `${updateDir}/route.php`;
|
|
480
|
+
|
|
350
481
|
const updateRouteContent = `<?php
|
|
351
482
|
|
|
352
483
|
use Lib\\Prisma\\Classes\\Prisma;
|
|
@@ -356,29 +487,65 @@ use Lib\\Request;
|
|
|
356
487
|
|
|
357
488
|
$prisma = Prisma::getInstance();
|
|
358
489
|
$id = Request::$dynamicParams->id ?? null;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
490
|
+
|
|
491
|
+
// Perform validation for the ID
|
|
492
|
+
if (!Validator::int($id)) {
|
|
493
|
+
Boom::badRequest("Invalid id")->toResponse();
|
|
363
494
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
|
|
510
|
+
$data = [];
|
|
511
|
+
foreach ($fieldsWithTypesAndStatus as $field => $details) {
|
|
512
|
+
$isRequired = $details['required'];
|
|
513
|
+
$type = $details['type'];
|
|
514
|
+
$validationFn = $details['validate'];
|
|
515
|
+
|
|
516
|
+
// Check if the field is required and missing in the request
|
|
517
|
+
if ($isRequired && !isset(Request::$params->$field)) {
|
|
518
|
+
Boom::badRequest("Missing {$field}")->toResponse();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Check if the field is present in the request
|
|
522
|
+
if (isset(Request::$params->$field)) {
|
|
523
|
+
$value = Request::$params->$field;
|
|
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
|
+
}
|
|
369
533
|
}
|
|
370
534
|
|
|
535
|
+
// Update the record
|
|
371
536
|
$updated${modelName} = $prisma->${camelCaseModelName}->update([
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
${updatePayload}
|
|
375
|
-
]
|
|
537
|
+
'where' => ['${idFieldName}' => $id],
|
|
538
|
+
'data' => $data,
|
|
376
539
|
]);
|
|
377
540
|
|
|
541
|
+
// Handle potential internal server error
|
|
378
542
|
if (!$updated${modelName}) {
|
|
379
|
-
|
|
543
|
+
Boom::notFound()->toResponse();
|
|
380
544
|
}
|
|
381
|
-
|
|
545
|
+
|
|
546
|
+
// Return the updated record in JSON format
|
|
547
|
+
echo json_encode($updated${modelName});
|
|
548
|
+
`;
|
|
382
549
|
|
|
383
550
|
writeFileSync(
|
|
384
551
|
resolve(__dirname, `../${updateRoutePath}`),
|
|
@@ -403,7 +570,7 @@ ${idValidationLogic}
|
|
|
403
570
|
|
|
404
571
|
$deleted${modelName} = $prisma->${camelCaseModelName}->delete([
|
|
405
572
|
'where' => [
|
|
406
|
-
'
|
|
573
|
+
'${idFieldName}' => $id
|
|
407
574
|
]
|
|
408
575
|
]);
|
|
409
576
|
|
|
@@ -421,6 +588,33 @@ echo json_encode($deleted${modelName});`;
|
|
|
421
588
|
|
|
422
589
|
async function promptUserForGenerationOptions() {
|
|
423
590
|
const response = await prompts([
|
|
591
|
+
{
|
|
592
|
+
type: "confirm",
|
|
593
|
+
name: "generateApisOnly",
|
|
594
|
+
message: "Do you want to generate swagger docs only?",
|
|
595
|
+
initial: false,
|
|
596
|
+
},
|
|
597
|
+
]);
|
|
598
|
+
|
|
599
|
+
// If the user wants to generate only Swagger docs
|
|
600
|
+
if (response.generateApisOnly) {
|
|
601
|
+
// Update the configuration
|
|
602
|
+
prismaSchemaConfigJson.generateSwaggerDocsOnly = true;
|
|
603
|
+
|
|
604
|
+
// Save the updated settings back to the JSON file if needed
|
|
605
|
+
writeFileSync(
|
|
606
|
+
resolve(__dirname, "./prisma-schema-config.json"),
|
|
607
|
+
JSON.stringify(prismaSchemaConfigJson, null, 2),
|
|
608
|
+
"utf-8"
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
// Generate Swagger docs and exit
|
|
612
|
+
await swaggerConfig();
|
|
613
|
+
exit(0); // Exit the process here
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// If the user did not select generateApisOnly, ask for other options
|
|
617
|
+
const otherResponses = await prompts([
|
|
424
618
|
{
|
|
425
619
|
type: "confirm",
|
|
426
620
|
name: "generateEndpoints",
|
|
@@ -435,11 +629,12 @@ async function promptUserForGenerationOptions() {
|
|
|
435
629
|
},
|
|
436
630
|
]);
|
|
437
631
|
|
|
438
|
-
// Update the configuration based on
|
|
439
|
-
prismaSchemaConfigJson.
|
|
440
|
-
prismaSchemaConfigJson.
|
|
632
|
+
// Update the configuration based on other responses
|
|
633
|
+
prismaSchemaConfigJson.generateSwaggerDocsOnly = false;
|
|
634
|
+
prismaSchemaConfigJson.generateEndpoints = otherResponses.generateEndpoints;
|
|
635
|
+
prismaSchemaConfigJson.generatePhpClasses = otherResponses.generatePhpClasses;
|
|
441
636
|
|
|
442
|
-
//
|
|
637
|
+
// Save the updated settings back to the JSON file
|
|
443
638
|
writeFileSync(
|
|
444
639
|
resolve(__dirname, "./prisma-schema-config.json"),
|
|
445
640
|
JSON.stringify(prismaSchemaConfigJson, null, 2),
|
|
@@ -2,7 +2,7 @@ import { createProxyMiddleware } from "http-proxy-middleware";
|
|
|
2
2
|
import { writeFileSync } from "fs";
|
|
3
3
|
import chokidar from "chokidar";
|
|
4
4
|
import browserSync, { BrowserSyncInstance } from "browser-sync";
|
|
5
|
-
import
|
|
5
|
+
import prismaPhpConfigJson from "../prisma-php.json";
|
|
6
6
|
import { generateFileListJson } from "./files-list.js";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { getFileMeta } from "./utils.js";
|
|
@@ -43,7 +43,7 @@ bs.init(
|
|
|
43
43
|
next();
|
|
44
44
|
},
|
|
45
45
|
createProxyMiddleware({
|
|
46
|
-
target:
|
|
46
|
+
target: prismaPhpConfigJson.bsTarget,
|
|
47
47
|
changeOrigin: true,
|
|
48
48
|
pathRewrite: {},
|
|
49
49
|
}),
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"swaggerDocsDir": "src/app/swagger-docs/apis",
|
|
3
|
-
"skipDefaultName": [
|
|
4
|
-
|
|
3
|
+
"skipDefaultName": [
|
|
4
|
+
"autoincrement",
|
|
5
|
+
"cuid",
|
|
6
|
+
"uuid",
|
|
7
|
+
"now"
|
|
8
|
+
],
|
|
9
|
+
"skipByPropertyValue": {
|
|
10
|
+
"isUpdatedAt": true
|
|
11
|
+
},
|
|
12
|
+
"skipFields": [],
|
|
13
|
+
"generateSwaggerDocsOnly": true,
|
|
5
14
|
"generateEndpoints": false,
|
|
6
|
-
"generatePhpClasses":
|
|
7
|
-
}
|
|
15
|
+
"generatePhpClasses": false
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, unlink, writeFile } from "fs";
|
|
2
2
|
import { join, basename, dirname, normalize, sep } from "path";
|
|
3
|
-
import
|
|
3
|
+
import prismaPhpConfigJson from "../prisma-php.json";
|
|
4
4
|
import { getFileMeta } from "./utils.js";
|
|
5
5
|
|
|
6
6
|
const { __dirname } = getFileMeta();
|
|
@@ -15,20 +15,23 @@ function updateProjectNameInConfig(
|
|
|
15
15
|
const filePathDir = dirname(filePath);
|
|
16
16
|
|
|
17
17
|
// Update the projectName directly in the imported config
|
|
18
|
-
|
|
18
|
+
prismaPhpConfigJson.projectName = newProjectName;
|
|
19
19
|
|
|
20
20
|
// Update other paths
|
|
21
|
-
|
|
21
|
+
prismaPhpConfigJson.projectRootPath = filePathDir;
|
|
22
22
|
|
|
23
|
-
const targetPath = getTargetPath(
|
|
23
|
+
const targetPath = getTargetPath(
|
|
24
|
+
filePathDir,
|
|
25
|
+
prismaPhpConfigJson.phpEnvironment
|
|
26
|
+
);
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
prismaPhpConfigJson.bsTarget = `http://localhost${targetPath}`;
|
|
29
|
+
prismaPhpConfigJson.bsPathRewrite["^/"] = targetPath;
|
|
27
30
|
|
|
28
31
|
// Save the updated config back to the JSON file
|
|
29
32
|
writeFile(
|
|
30
33
|
filePath,
|
|
31
|
-
JSON.stringify(
|
|
34
|
+
JSON.stringify(prismaPhpConfigJson, null, 2),
|
|
32
35
|
"utf8",
|
|
33
36
|
(err) => {
|
|
34
37
|
if (err) {
|
|
@@ -3,7 +3,8 @@ import { writeFileSync } from "fs";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { getFileMeta } from "./utils.js";
|
|
6
|
-
import
|
|
6
|
+
import bsConfigJson from "./bs-config.json";
|
|
7
|
+
import prismaPhpConfigJson from "../prisma-php.json";
|
|
7
8
|
|
|
8
9
|
const { __dirname } = getFileMeta();
|
|
9
10
|
|
|
@@ -23,7 +24,7 @@ export async function swaggerConfig(): Promise<void> {
|
|
|
23
24
|
},
|
|
24
25
|
servers: [
|
|
25
26
|
{
|
|
26
|
-
url:
|
|
27
|
+
url: bsConfigJson.local, // For Development
|
|
27
28
|
description: "Development Server",
|
|
28
29
|
},
|
|
29
30
|
{
|
|
@@ -65,5 +66,5 @@ export async function swaggerConfig(): Promise<void> {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
swaggerConfig();
|
|
69
|
+
if (prismaPhpConfigJson.swaggerDocs && !prismaPhpConfigJson.prisma)
|
|
70
|
+
swaggerConfig();
|
|
@@ -13,10 +13,10 @@ use Lib\Request;
|
|
|
13
13
|
|
|
14
14
|
class Auth
|
|
15
15
|
{
|
|
16
|
-
public const PAYLOAD_NAME = '
|
|
16
|
+
public const PAYLOAD_NAME = 'payload_name_8639D';
|
|
17
17
|
public const ROLE_NAME = '';
|
|
18
|
-
public const PAYLOAD_SESSION_KEY = '
|
|
19
|
-
public const COOKIE_NAME = '
|
|
18
|
+
public const PAYLOAD_SESSION_KEY = 'payload_session_key_2183A';
|
|
19
|
+
public const COOKIE_NAME = 'cookie_name_D36E5';
|
|
20
20
|
|
|
21
21
|
private static ?Auth $instance = null;
|
|
22
22
|
private const PPHPAUTH = 'pphpauth';
|
|
@@ -2,23 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Lib;
|
|
4
4
|
|
|
5
|
+
class BSPathRewrite
|
|
6
|
+
{
|
|
7
|
+
public string $pattern;
|
|
8
|
+
public string $replacement;
|
|
9
|
+
|
|
10
|
+
public function __construct(array $data)
|
|
11
|
+
{
|
|
12
|
+
$this->pattern = $data['pattern'] ?? '';
|
|
13
|
+
$this->replacement = $data['replacement'] ?? '';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class PrismaSettings
|
|
18
|
+
{
|
|
19
|
+
public string $projectName;
|
|
20
|
+
public string $projectRootPath;
|
|
21
|
+
public string $phpEnvironment;
|
|
22
|
+
public string $phpRootPathExe;
|
|
23
|
+
public string $phpGenerateClassPath;
|
|
24
|
+
public string $bsTarget;
|
|
25
|
+
public BSPathRewrite $bsPathRewrite;
|
|
26
|
+
public bool $backendOnly;
|
|
27
|
+
public bool $swaggerDocs;
|
|
28
|
+
public bool $tailwindcss;
|
|
29
|
+
public bool $websocket;
|
|
30
|
+
public bool $prisma;
|
|
31
|
+
public bool $docker;
|
|
32
|
+
public string $version;
|
|
33
|
+
public array $excludeFiles;
|
|
34
|
+
|
|
35
|
+
public function __construct(array $data)
|
|
36
|
+
{
|
|
37
|
+
$this->projectName = $data['projectName'] ?? '';
|
|
38
|
+
$this->projectRootPath = $data['projectRootPath'] ?? '';
|
|
39
|
+
$this->phpEnvironment = $data['phpEnvironment'] ?? '';
|
|
40
|
+
$this->phpRootPathExe = $data['phpRootPathExe'] ?? '';
|
|
41
|
+
$this->phpGenerateClassPath = $data['phpGenerateClassPath'] ?? '';
|
|
42
|
+
$this->bsTarget = $data['bsTarget'] ?? '';
|
|
43
|
+
$this->bsPathRewrite = new BSPathRewrite($data['bsPathRewrite'] ?? []);
|
|
44
|
+
$this->backendOnly = $data['backendOnly'] ?? false;
|
|
45
|
+
$this->swaggerDocs = $data['swaggerDocs'] ?? true;
|
|
46
|
+
$this->tailwindcss = $data['tailwindcss'] ?? true;
|
|
47
|
+
$this->websocket = $data['websocket'] ?? true;
|
|
48
|
+
$this->prisma = $data['prisma'] ?? true;
|
|
49
|
+
$this->docker = $data['docker'] ?? false;
|
|
50
|
+
$this->version = $data['version'] ?? '';
|
|
51
|
+
$this->excludeFiles = $data['excludeFiles'] ?? [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
5
55
|
class PrismaPHPSettings
|
|
6
56
|
{
|
|
7
57
|
/**
|
|
8
58
|
* The settings from the prisma-php.json file.
|
|
9
59
|
*
|
|
10
|
-
* @var
|
|
11
|
-
* @access public
|
|
12
|
-
* @static
|
|
60
|
+
* @var PrismaSettings
|
|
13
61
|
*/
|
|
14
|
-
public static
|
|
62
|
+
public static PrismaSettings $option;
|
|
15
63
|
|
|
16
64
|
/**
|
|
17
65
|
* The list of route files from the files-list.json file.
|
|
18
66
|
*
|
|
19
67
|
* @var array
|
|
20
|
-
* @access public
|
|
21
|
-
* @static
|
|
22
68
|
*/
|
|
23
69
|
public static array $routeFiles = [];
|
|
24
70
|
|
|
@@ -28,20 +74,28 @@ class PrismaPHPSettings
|
|
|
28
74
|
self::$routeFiles = self::getRoutesFileList();
|
|
29
75
|
}
|
|
30
76
|
|
|
31
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Get Prisma settings from the JSON file.
|
|
79
|
+
*
|
|
80
|
+
* @return PrismaSettings
|
|
81
|
+
* @throws Exception if the JSON file cannot be decoded.
|
|
82
|
+
*/
|
|
83
|
+
private static function getPrismaSettings(): PrismaSettings
|
|
32
84
|
{
|
|
33
85
|
$prismaPHPSettingsJson = DOCUMENT_PATH . '/prisma-php.json';
|
|
34
86
|
|
|
35
|
-
if (file_exists($prismaPHPSettingsJson)) {
|
|
36
|
-
|
|
37
|
-
|
|
87
|
+
if (!file_exists($prismaPHPSettingsJson)) {
|
|
88
|
+
throw new \Exception("Settings file not found: $prismaPHPSettingsJson");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$jsonContent = file_get_contents($prismaPHPSettingsJson);
|
|
92
|
+
$decodedJson = json_decode($jsonContent, true);
|
|
38
93
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
return new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
|
|
43
|
-
}
|
|
94
|
+
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
95
|
+
throw new \Exception("Failed to decode JSON: " . json_last_error_msg());
|
|
44
96
|
}
|
|
97
|
+
|
|
98
|
+
return new PrismaSettings($decodedJson);
|
|
45
99
|
}
|
|
46
100
|
|
|
47
101
|
private static function getRoutesFileList(): array
|
package/dist/src/Lib/Request.php
CHANGED
|
@@ -195,8 +195,36 @@ class Request
|
|
|
195
195
|
*/
|
|
196
196
|
private static function isAjaxRequest(): bool
|
|
197
197
|
{
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
$isAjax = false;
|
|
199
|
+
|
|
200
|
+
// Check for standard AJAX header
|
|
201
|
+
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
|
|
202
|
+
$isAjax = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check for common AJAX content types
|
|
206
|
+
if (!empty($_SERVER['CONTENT_TYPE'])) {
|
|
207
|
+
$ajaxContentTypes = [
|
|
208
|
+
'application/json',
|
|
209
|
+
'application/x-www-form-urlencoded',
|
|
210
|
+
'multipart/form-data',
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
foreach ($ajaxContentTypes as $contentType) {
|
|
214
|
+
if (strpos($_SERVER['CONTENT_TYPE'], $contentType) !== false) {
|
|
215
|
+
$isAjax = true;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check for common AJAX request methods
|
|
222
|
+
$ajaxMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
223
|
+
if (in_array(strtoupper($_SERVER['REQUEST_METHOD']), $ajaxMethods)) {
|
|
224
|
+
$isAjax = true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return $isAjax;
|
|
200
228
|
}
|
|
201
229
|
|
|
202
230
|
/**
|
|
@@ -217,8 +245,8 @@ class Request
|
|
|
217
245
|
{
|
|
218
246
|
$serverFetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
|
|
219
247
|
if (isset($serverFetchSite) && $serverFetchSite === 'same-origin') {
|
|
220
|
-
$headers = getallheaders();
|
|
221
|
-
return isset($headers['http_pphp_x_file_request']) &&
|
|
248
|
+
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
|
249
|
+
return isset($headers['http_pphp_x_file_request']) && $headers['http_pphp_x_file_request'] === 'true';
|
|
222
250
|
}
|
|
223
251
|
|
|
224
252
|
return false;
|