create-prisma-php-app 1.26.0 → 1.26.2
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 +3 -3
- package/dist/index.js +16 -2
- package/dist/settings/auto-swagger-docs.ts +520 -0
- package/dist/settings/prisma-schema-config.json +7 -0
- package/dist/settings/prisma-schema.json +103 -0
- package/dist/settings/prisma-sdk.ts +24 -0
- package/dist/src/Lib/MainLayout.php +2 -2
- package/dist/src/Lib/PrismaPHPSettings.php +12 -1
- package/dist/src/Lib/Request.php +9 -7
- package/package.json +1 -1
package/dist/bootstrap.php
CHANGED
|
@@ -161,7 +161,7 @@ function getFilePrecedence()
|
|
|
161
161
|
|
|
162
162
|
function uriExtractor(string $scriptUrl): string
|
|
163
163
|
{
|
|
164
|
-
$projectName = PrismaPHPSettings::$option
|
|
164
|
+
$projectName = PrismaPHPSettings::$option->projectName ?? '';
|
|
165
165
|
if (empty($projectName)) {
|
|
166
166
|
return "/";
|
|
167
167
|
}
|
|
@@ -675,7 +675,7 @@ try {
|
|
|
675
675
|
authenticateUserToken();
|
|
676
676
|
|
|
677
677
|
if (empty($_contentToInclude)) {
|
|
678
|
-
if (!Request::$isXFileRequest && PrismaPHPSettings::$option
|
|
678
|
+
if (!Request::$isXFileRequest && PrismaPHPSettings::$option->backendOnly) {
|
|
679
679
|
// Set the header and output a JSON response for permission denied
|
|
680
680
|
header('Content-Type: application/json');
|
|
681
681
|
echo json_encode([
|
|
@@ -701,7 +701,7 @@ try {
|
|
|
701
701
|
}
|
|
702
702
|
exit;
|
|
703
703
|
}
|
|
704
|
-
} else if (PrismaPHPSettings::$option
|
|
704
|
+
} else if (PrismaPHPSettings::$option->backendOnly) {
|
|
705
705
|
// Set the header and output a JSON response for file not found
|
|
706
706
|
header('Content-Type: application/json');
|
|
707
707
|
echo json_encode([
|
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=[];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&&(n.scripts={...n.scripts,"create-swagger-docs":"tsx settings/swagger-config.ts"});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;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.
|
|
@@ -129,6 +129,9 @@ async function main() {
|
|
|
129
129
|
if (answer.swaggerDocs) {
|
|
130
130
|
dependencies.push("swagger-jsdoc", "@types/swagger-jsdoc");
|
|
131
131
|
}
|
|
132
|
+
if (answer.swaggerDocs && answer.prisma) {
|
|
133
|
+
dependencies.push("prompts", "@types/prompts", "@prisma/internals");
|
|
134
|
+
}
|
|
132
135
|
if (answer.tailwindcss) {
|
|
133
136
|
dependencies.push(
|
|
134
137
|
"tailwindcss",
|
|
@@ -175,6 +178,11 @@ async function main() {
|
|
|
175
178
|
`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${swaggerDocsPath}`,
|
|
176
179
|
{ stdio: "inherit" }
|
|
177
180
|
);
|
|
181
|
+
// delete the folder .git
|
|
182
|
+
fs.rmSync(path.join(swaggerDocsPath, ".git"), {
|
|
183
|
+
recursive: true,
|
|
184
|
+
force: true,
|
|
185
|
+
});
|
|
178
186
|
}
|
|
179
187
|
if (updateAnswer?.isUpdate) {
|
|
180
188
|
const updateUninstallDependencies = [];
|
|
@@ -220,7 +228,13 @@ async function main() {
|
|
|
220
228
|
console.log(`${file} does not exist.`);
|
|
221
229
|
}
|
|
222
230
|
});
|
|
223
|
-
updateUninstallDependencies.push(
|
|
231
|
+
updateUninstallDependencies.push(
|
|
232
|
+
"swagger-jsdoc",
|
|
233
|
+
"@types/swagger-jsdoc",
|
|
234
|
+
"prompts",
|
|
235
|
+
"@types/prompts",
|
|
236
|
+
"@prisma/internals"
|
|
237
|
+
);
|
|
224
238
|
}
|
|
225
239
|
if (!updateAnswer.tailwindcss) {
|
|
226
240
|
const tailwindFiles = ["postcss.config.js", "tailwind.config.js"];
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import { prismaSdk } from "./prisma-sdk.js";
|
|
6
|
+
import { swaggerConfig } from "./swagger-config.js";
|
|
7
|
+
import { getFileMeta } from "./utils.js";
|
|
8
|
+
import prismaSchemaConfigJson from "./prisma-schema-config.json";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
|
|
11
|
+
const { __dirname } = getFileMeta();
|
|
12
|
+
const prismaSchemaJsonPath = resolve(__dirname, "./prisma-schema.json");
|
|
13
|
+
|
|
14
|
+
// Function to generate properties for Swagger annotations
|
|
15
|
+
function generateProperties(fields: any[]): {
|
|
16
|
+
properties: string;
|
|
17
|
+
allProperties: string;
|
|
18
|
+
} {
|
|
19
|
+
let properties = "";
|
|
20
|
+
let allProperties = "";
|
|
21
|
+
|
|
22
|
+
fields.forEach((field) => {
|
|
23
|
+
if (field.kind === "object") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const example = field.example || (field.isId ? 1 : `"${field.name}"`);
|
|
28
|
+
allProperties += `
|
|
29
|
+
* ${field.name}:
|
|
30
|
+
* type: ${field.type.toLowerCase()}
|
|
31
|
+
* example: ${example}`;
|
|
32
|
+
|
|
33
|
+
if (prismaSchemaConfigJson.skipFields.includes(field.name)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (prismaSchemaConfigJson.skipDefaultName.includes(field.default?.name)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
properties += `
|
|
42
|
+
* ${field.name}:
|
|
43
|
+
* type: ${field.type.toLowerCase()}
|
|
44
|
+
* example: ${example}`;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { properties, allProperties };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toKebabCase(str: string): string {
|
|
51
|
+
return str
|
|
52
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
53
|
+
.replace(/[\s_]+/g, "-")
|
|
54
|
+
.toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Function to generate Swagger annotation for a CRUD operation
|
|
58
|
+
function generateSwaggerAnnotation(modelName: string, fields: any[]): string {
|
|
59
|
+
const { properties, allProperties } = generateProperties(fields);
|
|
60
|
+
const kebabCaseModelName = toKebabCase(modelName);
|
|
61
|
+
|
|
62
|
+
return `/**
|
|
63
|
+
* @swagger
|
|
64
|
+
* tags:
|
|
65
|
+
* name: ${modelName}
|
|
66
|
+
* description: ${modelName} management API
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @swagger
|
|
71
|
+
* /${kebabCaseModelName}:
|
|
72
|
+
* get:
|
|
73
|
+
* summary: Retrieve a list of ${modelName}
|
|
74
|
+
* tags:
|
|
75
|
+
* - ${modelName}
|
|
76
|
+
* responses:
|
|
77
|
+
* 200:
|
|
78
|
+
* description: A list of ${modelName}
|
|
79
|
+
* content:
|
|
80
|
+
* application/json:
|
|
81
|
+
* schema:
|
|
82
|
+
* type: array
|
|
83
|
+
* items:
|
|
84
|
+
* type: object
|
|
85
|
+
* properties:${allProperties}
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @swagger
|
|
90
|
+
* /${kebabCaseModelName}/{id}:
|
|
91
|
+
* get:
|
|
92
|
+
* summary: Retrieve a single ${modelName} by ID
|
|
93
|
+
* tags:
|
|
94
|
+
* - ${modelName}
|
|
95
|
+
* parameters:
|
|
96
|
+
* - in: path
|
|
97
|
+
* name: id
|
|
98
|
+
* required: true
|
|
99
|
+
* description: The ${modelName} ID
|
|
100
|
+
* schema:
|
|
101
|
+
* type: string
|
|
102
|
+
* responses:
|
|
103
|
+
* 200:
|
|
104
|
+
* description: A single ${modelName} object
|
|
105
|
+
* content:
|
|
106
|
+
* application/json:
|
|
107
|
+
* schema:
|
|
108
|
+
* type: object
|
|
109
|
+
* properties:${allProperties}
|
|
110
|
+
* 404:
|
|
111
|
+
* description: ${modelName} not found
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @swagger
|
|
116
|
+
* /${kebabCaseModelName}/create:
|
|
117
|
+
* post:
|
|
118
|
+
* summary: Create a new ${modelName}
|
|
119
|
+
* tags:
|
|
120
|
+
* - ${modelName}
|
|
121
|
+
* requestBody:
|
|
122
|
+
* required: true
|
|
123
|
+
* content:
|
|
124
|
+
* application/json:
|
|
125
|
+
* schema:
|
|
126
|
+
* type: object
|
|
127
|
+
* properties:${properties}
|
|
128
|
+
* responses:
|
|
129
|
+
* 201:
|
|
130
|
+
* description: ${modelName} created successfully.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @swagger
|
|
135
|
+
* /${kebabCaseModelName}/update/{id}:
|
|
136
|
+
* put:
|
|
137
|
+
* summary: Update a ${modelName} by ID
|
|
138
|
+
* tags:
|
|
139
|
+
* - ${modelName}
|
|
140
|
+
* parameters:
|
|
141
|
+
* - in: path
|
|
142
|
+
* name: id
|
|
143
|
+
* required: true
|
|
144
|
+
* description: The ${modelName} ID
|
|
145
|
+
* schema:
|
|
146
|
+
* type: string
|
|
147
|
+
* requestBody:
|
|
148
|
+
* required: true
|
|
149
|
+
* content:
|
|
150
|
+
* application/json:
|
|
151
|
+
* schema:
|
|
152
|
+
* type: object
|
|
153
|
+
* properties:${properties}
|
|
154
|
+
* responses:
|
|
155
|
+
* 200:
|
|
156
|
+
* description: ${modelName} updated successfully.
|
|
157
|
+
* 404:
|
|
158
|
+
* description: ${modelName} not found
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @swagger
|
|
163
|
+
* /${kebabCaseModelName}/delete/{id}:
|
|
164
|
+
* delete:
|
|
165
|
+
* summary: Delete a ${modelName} by ID
|
|
166
|
+
* tags:
|
|
167
|
+
* - ${modelName}
|
|
168
|
+
* parameters:
|
|
169
|
+
* - in: path
|
|
170
|
+
* name: id
|
|
171
|
+
* required: true
|
|
172
|
+
* description: The ${modelName} ID
|
|
173
|
+
* schema:
|
|
174
|
+
* type: string
|
|
175
|
+
* responses:
|
|
176
|
+
* 204:
|
|
177
|
+
* description: ${modelName} successfully deleted
|
|
178
|
+
* 404:
|
|
179
|
+
* description: ${modelName} not found
|
|
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 };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Function to generate dynamic ID validation logic for update and find-by-ID routes
|
|
224
|
+
function generateIdValidationLogic(idField: any) {
|
|
225
|
+
const fieldType = idField.type.toLowerCase();
|
|
226
|
+
|
|
227
|
+
if (["cuid", "uuid", "autoincrement"].includes(idField.default?.name)) {
|
|
228
|
+
return `
|
|
229
|
+
if (!Validator::${fieldType}($id)) {
|
|
230
|
+
Boom::badRequest("Invalid ${idField.name}")->toResponse();
|
|
231
|
+
}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return ""; // No specific validation needed otherwise
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Function to generate endpoints for a model
|
|
238
|
+
function generateEndpoints(modelName: string, fields: any[]): void {
|
|
239
|
+
const kebabCasedModelName = toKebabCase(modelName);
|
|
240
|
+
const camelCaseModelName =
|
|
241
|
+
modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
242
|
+
const baseDir = `src/app/${kebabCasedModelName}`;
|
|
243
|
+
const idField = fields.find((field) => field.isId);
|
|
244
|
+
const baseDirPath = resolve(__dirname, `../${baseDir}`);
|
|
245
|
+
|
|
246
|
+
mkdirSync(baseDirPath, { recursive: true });
|
|
247
|
+
|
|
248
|
+
// Endpoint: GET /{kebabCasedModelName}
|
|
249
|
+
const listRoutePath = `${baseDir}/route.php`;
|
|
250
|
+
const listRouteContent = `<?php
|
|
251
|
+
|
|
252
|
+
use Lib\\Prisma\\Classes\\Prisma;
|
|
253
|
+
|
|
254
|
+
$prisma = Prisma::getInstance();
|
|
255
|
+
|
|
256
|
+
$${camelCaseModelName} = $prisma->${camelCaseModelName}->findMany();
|
|
257
|
+
echo json_encode($${camelCaseModelName});`;
|
|
258
|
+
writeFileSync(
|
|
259
|
+
resolve(__dirname, `../${listRoutePath}`),
|
|
260
|
+
listRouteContent,
|
|
261
|
+
"utf-8"
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Endpoint: GET /{kebabCasedModelName}/{id}
|
|
265
|
+
const idDir = `${baseDir}/[id]`;
|
|
266
|
+
mkdirSync(resolve(__dirname, `../${idDir}`), { recursive: true });
|
|
267
|
+
const idRoutePath = `${idDir}/route.php`;
|
|
268
|
+
const idValidationLogic = generateIdValidationLogic(idField);
|
|
269
|
+
const idRouteContent = `<?php
|
|
270
|
+
|
|
271
|
+
use Lib\\Prisma\\Classes\\Prisma;
|
|
272
|
+
use Lib\\Validator;
|
|
273
|
+
use Lib\\Headers\\Boom;
|
|
274
|
+
use Lib\\Request;
|
|
275
|
+
|
|
276
|
+
$prisma = Prisma::getInstance();
|
|
277
|
+
$id = Request::$dynamicParams->id ?? null;
|
|
278
|
+
${idValidationLogic}
|
|
279
|
+
|
|
280
|
+
$${camelCaseModelName} = $prisma->${camelCaseModelName}->findUnique([
|
|
281
|
+
'where' => [
|
|
282
|
+
'id' => $id
|
|
283
|
+
]
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
if (!$${camelCaseModelName}) {
|
|
287
|
+
Boom::notFound()->toResponse();
|
|
288
|
+
}
|
|
289
|
+
echo json_encode($${camelCaseModelName});`;
|
|
290
|
+
|
|
291
|
+
writeFileSync(
|
|
292
|
+
resolve(__dirname, `../${idRoutePath}`),
|
|
293
|
+
idRouteContent,
|
|
294
|
+
"utf-8"
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Endpoint: POST /{kebabCasedModelName}/create
|
|
298
|
+
const {
|
|
299
|
+
validations: createValidations,
|
|
300
|
+
payload: createPayload,
|
|
301
|
+
variableAssignments,
|
|
302
|
+
} = generateValidationAndPayload(fields);
|
|
303
|
+
|
|
304
|
+
const createDir = `${baseDir}/create`;
|
|
305
|
+
mkdirSync(resolve(__dirname, `../${createDir}`), { recursive: true });
|
|
306
|
+
const createRoutePath = `${createDir}/route.php`;
|
|
307
|
+
const createRouteContent = `<?php
|
|
308
|
+
|
|
309
|
+
use Lib\\Prisma\\Classes\\Prisma;
|
|
310
|
+
use Lib\\Validator;
|
|
311
|
+
use Lib\\Headers\\Boom;
|
|
312
|
+
use Lib\\Request;
|
|
313
|
+
|
|
314
|
+
$prisma = Prisma::getInstance();
|
|
315
|
+
${
|
|
316
|
+
variableAssignments.length > 0
|
|
317
|
+
? variableAssignments
|
|
318
|
+
: "// Your custom variable assignments here"
|
|
319
|
+
}
|
|
320
|
+
${
|
|
321
|
+
createValidations.length > 0
|
|
322
|
+
? createValidations
|
|
323
|
+
: "\n// Your custom validation logic here"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
$new${modelName} = $prisma->${camelCaseModelName}->create([
|
|
327
|
+
'data' => [
|
|
328
|
+
${createPayload}
|
|
329
|
+
]
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
if (!$new${modelName}) {
|
|
333
|
+
Boom::internal()->toResponse();
|
|
334
|
+
}
|
|
335
|
+
echo json_encode($new${modelName});`;
|
|
336
|
+
|
|
337
|
+
writeFileSync(
|
|
338
|
+
resolve(__dirname, `../${createRoutePath}`),
|
|
339
|
+
createRouteContent,
|
|
340
|
+
"utf-8"
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Endpoint: PUT /{kebabCasedModelName}/update/{id}
|
|
344
|
+
const { validations: updateValidations, payload: updatePayload } =
|
|
345
|
+
generateValidationAndPayload(fields);
|
|
346
|
+
|
|
347
|
+
const updateDir = `${baseDir}/update/[id]`;
|
|
348
|
+
mkdirSync(resolve(__dirname, `../${updateDir}`), { recursive: true });
|
|
349
|
+
const updateRoutePath = `${updateDir}/route.php`;
|
|
350
|
+
const updateRouteContent = `<?php
|
|
351
|
+
|
|
352
|
+
use Lib\\Prisma\\Classes\\Prisma;
|
|
353
|
+
use Lib\\Validator;
|
|
354
|
+
use Lib\\Headers\\Boom;
|
|
355
|
+
use Lib\\Request;
|
|
356
|
+
|
|
357
|
+
$prisma = Prisma::getInstance();
|
|
358
|
+
$id = Request::$dynamicParams->id ?? null;
|
|
359
|
+
${
|
|
360
|
+
variableAssignments.length > 0
|
|
361
|
+
? variableAssignments
|
|
362
|
+
: "// Your custom variable assignments here"
|
|
363
|
+
}
|
|
364
|
+
${idValidationLogic}
|
|
365
|
+
${
|
|
366
|
+
updateValidations.length > 0
|
|
367
|
+
? updateValidations
|
|
368
|
+
: "\n// Your custom validation logic here"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
$updated${modelName} = $prisma->${camelCaseModelName}->update([
|
|
372
|
+
'where' => ['id' => $id],
|
|
373
|
+
'data' => [
|
|
374
|
+
${updatePayload}
|
|
375
|
+
]
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
if (!$updated${modelName}) {
|
|
379
|
+
Boom::notFound()->toResponse();
|
|
380
|
+
}
|
|
381
|
+
echo json_encode($updated${modelName});`;
|
|
382
|
+
|
|
383
|
+
writeFileSync(
|
|
384
|
+
resolve(__dirname, `../${updateRoutePath}`),
|
|
385
|
+
updateRouteContent,
|
|
386
|
+
"utf-8"
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// Endpoint: DELETE /{kebabCasedModelName}/delete/{id}
|
|
390
|
+
const deleteDir = `${baseDir}/delete/[id]`;
|
|
391
|
+
mkdirSync(resolve(__dirname, `../${deleteDir}`), { recursive: true });
|
|
392
|
+
const deleteRoutePath = `${deleteDir}/route.php`;
|
|
393
|
+
const deleteRouteContent = `<?php
|
|
394
|
+
|
|
395
|
+
use Lib\\Prisma\\Classes\\Prisma;
|
|
396
|
+
use Lib\\Validator;
|
|
397
|
+
use Lib\\Headers\\Boom;
|
|
398
|
+
use Lib\\Request;
|
|
399
|
+
|
|
400
|
+
$prisma = Prisma::getInstance();
|
|
401
|
+
$id = Request::$dynamicParams->id ?? null;
|
|
402
|
+
${idValidationLogic}
|
|
403
|
+
|
|
404
|
+
$deleted${modelName} = $prisma->${camelCaseModelName}->delete([
|
|
405
|
+
'where' => [
|
|
406
|
+
'id' => $id
|
|
407
|
+
]
|
|
408
|
+
]);
|
|
409
|
+
|
|
410
|
+
if (!$deleted${modelName}) {
|
|
411
|
+
Boom::notFound()->toResponse();
|
|
412
|
+
}
|
|
413
|
+
echo json_encode($deleted${modelName});`;
|
|
414
|
+
|
|
415
|
+
writeFileSync(
|
|
416
|
+
resolve(__dirname, `../${deleteRoutePath}`),
|
|
417
|
+
deleteRouteContent,
|
|
418
|
+
"utf-8"
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function promptUserForGenerationOptions() {
|
|
423
|
+
const response = await prompts([
|
|
424
|
+
{
|
|
425
|
+
type: "confirm",
|
|
426
|
+
name: "generateEndpoints",
|
|
427
|
+
message: "Do you want to generate endpoints?",
|
|
428
|
+
initial: false,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
type: "confirm",
|
|
432
|
+
name: "generatePhpClasses",
|
|
433
|
+
message: "Do you want to generate PHP classes?",
|
|
434
|
+
initial: false,
|
|
435
|
+
},
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
// Update the configuration based on user input
|
|
439
|
+
prismaSchemaConfigJson.generateEndpoints = response.generateEndpoints;
|
|
440
|
+
prismaSchemaConfigJson.generatePhpClasses = response.generatePhpClasses;
|
|
441
|
+
|
|
442
|
+
// Optionally, you can save the updated settings back to the JSON file
|
|
443
|
+
writeFileSync(
|
|
444
|
+
resolve(__dirname, "./prisma-schema-config.json"),
|
|
445
|
+
JSON.stringify(prismaSchemaConfigJson, null, 2),
|
|
446
|
+
"utf-8"
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Function to read the updated Prisma JSON schema directly
|
|
451
|
+
function readUpdatedSchema() {
|
|
452
|
+
try {
|
|
453
|
+
const schemaContent = readFileSync(prismaSchemaJsonPath, "utf-8");
|
|
454
|
+
return JSON.parse(schemaContent);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.error("Error reading updated schema:", error);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function generateSwaggerDocs(modelsToGenerate: string[]): Promise<void> {
|
|
462
|
+
// Read the updated schema directly from the file
|
|
463
|
+
const updatedSchema = readUpdatedSchema();
|
|
464
|
+
if (!updatedSchema) {
|
|
465
|
+
console.error("Failed to read updated JSON schema.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const models = updatedSchema.datamodel.models;
|
|
470
|
+
|
|
471
|
+
if (modelsToGenerate.includes("all")) {
|
|
472
|
+
models.forEach((model: any) => {
|
|
473
|
+
generateAndSaveSwaggerDocsForModel(model);
|
|
474
|
+
});
|
|
475
|
+
} else {
|
|
476
|
+
modelsToGenerate.forEach((modelName) => {
|
|
477
|
+
const model = models.find((m: any) => m.name.toLowerCase() === modelName);
|
|
478
|
+
if (model) {
|
|
479
|
+
generateAndSaveSwaggerDocsForModel(model);
|
|
480
|
+
} else {
|
|
481
|
+
console.error(`Model "${modelName}" not found in the schema.`);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function generateAndSaveSwaggerDocsForModel(model: any): void {
|
|
488
|
+
const kebabCaseModelName = toKebabCase(model.name);
|
|
489
|
+
const swaggerAnnotation = generateSwaggerAnnotation(model.name, model.fields);
|
|
490
|
+
const whereToSave = `${prismaSchemaConfigJson.swaggerDocsDir}/${kebabCaseModelName}.js`;
|
|
491
|
+
const outputFilePath = resolve(__dirname, `../${whereToSave}`);
|
|
492
|
+
|
|
493
|
+
writeFileSync(outputFilePath, swaggerAnnotation, "utf-8");
|
|
494
|
+
console.log(
|
|
495
|
+
`Swagger annotations for model "${model.name}" generated at: ${chalk.blue(
|
|
496
|
+
whereToSave
|
|
497
|
+
)}`
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
if (prismaSchemaConfigJson.generateEndpoints) {
|
|
501
|
+
generateEndpoints(model.name, model.fields);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
await promptUserForGenerationOptions();
|
|
506
|
+
|
|
507
|
+
const args = process.argv.slice(2);
|
|
508
|
+
const modelsToGenerate =
|
|
509
|
+
args.length > 0 ? args.map((arg) => arg.toLowerCase()) : ["all"];
|
|
510
|
+
|
|
511
|
+
await prismaSdk();
|
|
512
|
+
await generateSwaggerDocs(modelsToGenerate);
|
|
513
|
+
await swaggerConfig();
|
|
514
|
+
|
|
515
|
+
if (prismaSchemaConfigJson.generatePhpClasses) {
|
|
516
|
+
spawn("npx", ["php", "generate", "class"], {
|
|
517
|
+
stdio: "inherit",
|
|
518
|
+
shell: true,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"datamodel": {
|
|
3
|
+
"enums": [],
|
|
4
|
+
"models": []
|
|
5
|
+
},
|
|
6
|
+
"schema": {
|
|
7
|
+
"inputObjectTypes": {},
|
|
8
|
+
"outputObjectTypes": {
|
|
9
|
+
"prisma": [
|
|
10
|
+
{
|
|
11
|
+
"name": "Query",
|
|
12
|
+
"fields": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "Mutation",
|
|
16
|
+
"fields": [
|
|
17
|
+
{
|
|
18
|
+
"name": "executeRaw",
|
|
19
|
+
"args": [
|
|
20
|
+
{
|
|
21
|
+
"name": "query",
|
|
22
|
+
"isRequired": true,
|
|
23
|
+
"isNullable": false,
|
|
24
|
+
"inputTypes": [
|
|
25
|
+
{
|
|
26
|
+
"type": "String",
|
|
27
|
+
"location": "scalar",
|
|
28
|
+
"isList": false
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "parameters",
|
|
34
|
+
"isRequired": false,
|
|
35
|
+
"isNullable": false,
|
|
36
|
+
"inputTypes": [
|
|
37
|
+
{
|
|
38
|
+
"type": "Json",
|
|
39
|
+
"location": "scalar",
|
|
40
|
+
"isList": false
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"isNullable": false,
|
|
46
|
+
"outputType": {
|
|
47
|
+
"type": "Json",
|
|
48
|
+
"location": "scalar",
|
|
49
|
+
"isList": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "queryRaw",
|
|
54
|
+
"args": [
|
|
55
|
+
{
|
|
56
|
+
"name": "query",
|
|
57
|
+
"isRequired": true,
|
|
58
|
+
"isNullable": false,
|
|
59
|
+
"inputTypes": [
|
|
60
|
+
{
|
|
61
|
+
"type": "String",
|
|
62
|
+
"location": "scalar",
|
|
63
|
+
"isList": false
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "parameters",
|
|
69
|
+
"isRequired": false,
|
|
70
|
+
"isNullable": false,
|
|
71
|
+
"inputTypes": [
|
|
72
|
+
{
|
|
73
|
+
"type": "Json",
|
|
74
|
+
"location": "scalar",
|
|
75
|
+
"isList": false
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"isNullable": false,
|
|
81
|
+
"outputType": {
|
|
82
|
+
"type": "Json",
|
|
83
|
+
"location": "scalar",
|
|
84
|
+
"isList": false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"enumTypes": {}
|
|
92
|
+
},
|
|
93
|
+
"mappings": {
|
|
94
|
+
"modelOperations": [],
|
|
95
|
+
"otherOperations": {
|
|
96
|
+
"read": [],
|
|
97
|
+
"write": [
|
|
98
|
+
"executeRaw",
|
|
99
|
+
"queryRaw"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import psdk from "@prisma/internals";
|
|
4
|
+
import { getFileMeta } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
const { __dirname } = getFileMeta();
|
|
7
|
+
const { getDMMF } = psdk;
|
|
8
|
+
const schemaPath: string = resolve(__dirname, "../prisma/schema.prisma");
|
|
9
|
+
const prismaSchemaJsonPath: string = resolve(__dirname, "prisma-schema.json");
|
|
10
|
+
|
|
11
|
+
export const prismaSdk = async (): Promise<void> => {
|
|
12
|
+
try {
|
|
13
|
+
const schema = readFileSync(schemaPath, "utf-8");
|
|
14
|
+
|
|
15
|
+
// Parse the schema into DMMF (Data Model Meta Format) and then convert to JSON
|
|
16
|
+
const dmmf = await getDMMF({ datamodel: schema });
|
|
17
|
+
|
|
18
|
+
// Write the DMMF schema to JSON
|
|
19
|
+
writeFileSync(prismaSchemaJsonPath, JSON.stringify(dmmf, null, 2));
|
|
20
|
+
console.log("Schema converted to JSON!");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Error parsing schema:", error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -9,8 +9,8 @@ class MainLayout
|
|
|
9
9
|
public static string $children = '';
|
|
10
10
|
public static string $childLayoutChildren = '';
|
|
11
11
|
|
|
12
|
-
private static $headScripts = [];
|
|
13
|
-
private static $footerScripts = [];
|
|
12
|
+
private static array $headScripts = [];
|
|
13
|
+
private static array $footerScripts = [];
|
|
14
14
|
private static array $customMetadata = [];
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -5,10 +5,21 @@ namespace Lib;
|
|
|
5
5
|
class PrismaPHPSettings
|
|
6
6
|
{
|
|
7
7
|
/**
|
|
8
|
+
* The settings from the prisma-php.json file.
|
|
8
9
|
*
|
|
10
|
+
* @var \stdClass
|
|
11
|
+
* @access public
|
|
12
|
+
* @static
|
|
9
13
|
*/
|
|
10
14
|
public static \ArrayObject $option;
|
|
11
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The list of route files from the files-list.json file.
|
|
18
|
+
*
|
|
19
|
+
* @var array
|
|
20
|
+
* @access public
|
|
21
|
+
* @static
|
|
22
|
+
*/
|
|
12
23
|
public static array $routeFiles = [];
|
|
13
24
|
|
|
14
25
|
public static function init(): void
|
|
@@ -28,7 +39,7 @@ class PrismaPHPSettings
|
|
|
28
39
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
29
40
|
return new \ArrayObject($decodedJson, \ArrayObject::ARRAY_AS_PROPS);
|
|
30
41
|
} else {
|
|
31
|
-
return new \ArrayObject([]);
|
|
42
|
+
return new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
}
|
package/dist/src/Lib/Request.php
CHANGED
|
@@ -17,10 +17,6 @@ class Request
|
|
|
17
17
|
* This property is used to hold request parameters that are passed to the request.
|
|
18
18
|
*
|
|
19
19
|
* Example usage:
|
|
20
|
-
* ```php
|
|
21
|
-
* Request::$params = new \ArrayObject(['id' => 123]);
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
20
|
* The parameters can be accessed using the following syntax:
|
|
25
21
|
* ```php
|
|
26
22
|
* $id = Request::$params['id'];
|
|
@@ -36,16 +32,22 @@ class Request
|
|
|
36
32
|
* This property is used to hold dynamic parameters that are passed to the request.
|
|
37
33
|
*
|
|
38
34
|
* Example usage:
|
|
39
|
-
*
|
|
40
35
|
* Single parameter:
|
|
41
36
|
* ```php
|
|
42
|
-
* Request::$dynamicParams
|
|
37
|
+
* $id = Request::$dynamicParams['id'];
|
|
38
|
+
* OR
|
|
39
|
+
* $id = Request::$dynamicParams->id;
|
|
43
40
|
* ```
|
|
44
41
|
*
|
|
45
42
|
* Multiple parameters:
|
|
46
43
|
* ```php
|
|
47
|
-
*
|
|
44
|
+
* $dynamicParams = Request::$dynamicParams;
|
|
45
|
+
* echo '<pre>';
|
|
46
|
+
* print_r($dynamicParams);
|
|
47
|
+
* echo '</pre>';
|
|
48
48
|
* ```
|
|
49
|
+
*
|
|
50
|
+
* The above code will output the dynamic parameters as an array, which can be useful for debugging purposes.
|
|
49
51
|
*/
|
|
50
52
|
public static \ArrayObject $dynamicParams;
|
|
51
53
|
|