create-prisma-php-app 1.28.7 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composer.json +8 -6
- package/dist/bootstrap.php +652 -682
- package/dist/index.js +51 -30
- package/dist/postcss.config.js +1 -3
- package/dist/prisma/seed.ts +2 -2
- package/dist/prisma-php.js +2 -42
- package/dist/settings/prisma-sdk.ts +4 -0
- package/dist/settings/project-name.ts +26 -2
- package/dist/src/Lib/AI/ChatGPTClient.php +32 -103
- package/dist/src/Lib/CacheHandler.php +121 -0
- package/dist/src/Lib/ErrorHandler.php +123 -0
- package/dist/src/Lib/MainLayout.php +23 -22
- package/dist/src/Lib/PHPX/TemplateCompiler.php +47 -24
- package/dist/src/Lib/PHPX/TwMerge.php +7 -1
- package/dist/src/Lib/Prisma/Classes/PPHPUtility.php +234 -105
- package/dist/src/Lib/Prisma/Model/IModel.php +15 -11
- package/dist/src/Lib/PrismaPHPSettings.php +44 -9
- package/dist/src/Lib/Request.php +1 -1
- package/dist/src/Lib/Validator.php +35 -7
- package/dist/src/app/css/tailwind.css +1 -3
- package/dist/src/app/index.php +2 -2
- package/dist/src/app/js/index.js +12 -1
- package/dist/src/app/layout.php +4 -7
- package/dist/src/app/not-found.php +1 -1
- package/package.json +1 -1
- package/tsconfig.json +5 -6
- package/dist/prisma-client-php/index.enc +0 -1
- package/dist/prisma-client-php/index.js +0 -19
- package/dist/prisma-client-php/key.enc +0 -1
- package/dist/tailwind.config.js +0 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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","calicastle/cuid":"^2.0"}),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")}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"},{src:"/app-gitignore",dest:"/.gitignore"}];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)}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}}
|
|
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";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php"],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,"calicastle/cuid":"^2.0.0"}),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")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateLocalStoreKey(){return randomBytes(16).toString("hex")}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 modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n },\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"},{src:"/app-gitignore",dest:"/.gitignore"}];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&&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="${generateAuthSecret()}"\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"\n\n# APP CACHE ENABLED - Set to true to enable caching - Default is false\nCACHE_ENABLED="false"\n# APP CACHE TTL - Set the cache time to live in seconds - Default is 600 seconds (10 minutes)\nCACHE_TTL="600"\n\n# LOCAL STORAGE KEY - Define a custom key for local storage.\n# If not set, it defaults to "pphp_local_store_59e13".\n# Spaces in the value will be replaced with underscores, and the key will be converted to lowercase automatically.\nLOCALSTORE_KEY="${generateLocalStoreKey()}"`;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)}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:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,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.
|
|
@@ -31,6 +31,31 @@ async function installDependencies(baseDir, dependencies, isDev = false) {
|
|
|
31
31
|
cwd: baseDir,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
const pinnedVersions = {
|
|
35
|
+
"@prisma/client": "^6.2.1",
|
|
36
|
+
"@types/browser-sync": "^2.29.0",
|
|
37
|
+
"@types/node": "^22.10.7",
|
|
38
|
+
"@types/prompts": "^2.4.9",
|
|
39
|
+
"@prisma/internals": "^6.2.1",
|
|
40
|
+
autoprefixer: "^10.4.20",
|
|
41
|
+
"browser-sync": "^3.0.3",
|
|
42
|
+
chalk: "^5.4.1",
|
|
43
|
+
"chokidar-cli": "^3.0.0",
|
|
44
|
+
"http-proxy-middleware": "^3.0.3",
|
|
45
|
+
"npm-run-all": "^4.1.5",
|
|
46
|
+
"php-parser": "^3.2.2",
|
|
47
|
+
prompts: "^2.4.2",
|
|
48
|
+
postcss: "^8.5.1",
|
|
49
|
+
"postcss-cli": "^11.0.0",
|
|
50
|
+
prisma: "^6.2.1",
|
|
51
|
+
tailwindcss: "^4.0.0",
|
|
52
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
53
|
+
tsx: "^4.19.2",
|
|
54
|
+
typescript: "^5.7.3",
|
|
55
|
+
};
|
|
56
|
+
function pkg(name) {
|
|
57
|
+
return pinnedVersions[name] ? `${name}@${pinnedVersions[name]}` : name;
|
|
58
|
+
}
|
|
34
59
|
async function main() {
|
|
35
60
|
try {
|
|
36
61
|
const args = process.argv.slice(2);
|
|
@@ -117,45 +142,45 @@ async function main() {
|
|
|
117
142
|
: path.join(currentDir, answer.projectName);
|
|
118
143
|
if (!projectName) process.chdir(answer.projectName);
|
|
119
144
|
const dependencies = [
|
|
120
|
-
"typescript",
|
|
121
|
-
"@types/node",
|
|
122
|
-
"tsx",
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"browser-sync",
|
|
128
|
-
"
|
|
129
|
-
"php-parser",
|
|
145
|
+
pkg("typescript"),
|
|
146
|
+
pkg("@types/node"),
|
|
147
|
+
pkg("tsx"),
|
|
148
|
+
pkg("http-proxy-middleware"),
|
|
149
|
+
pkg("chalk"),
|
|
150
|
+
pkg("npm-run-all"),
|
|
151
|
+
pkg("browser-sync"),
|
|
152
|
+
pkg("@types/browser-sync"),
|
|
153
|
+
pkg("php-parser"),
|
|
130
154
|
];
|
|
131
155
|
if (answer.swaggerDocs) {
|
|
132
|
-
dependencies.push("swagger-jsdoc", "@types/swagger-jsdoc");
|
|
156
|
+
dependencies.push(pkg("swagger-jsdoc"), pkg("@types/swagger-jsdoc"));
|
|
133
157
|
}
|
|
134
158
|
if (answer.swaggerDocs && answer.prisma) {
|
|
135
|
-
dependencies.push(
|
|
159
|
+
dependencies.push(
|
|
160
|
+
pkg("prompts"),
|
|
161
|
+
pkg("@types/prompts"),
|
|
162
|
+
pkg("@prisma/internals")
|
|
163
|
+
);
|
|
136
164
|
}
|
|
137
165
|
if (answer.tailwindcss) {
|
|
138
166
|
dependencies.push(
|
|
139
|
-
"tailwindcss",
|
|
140
|
-
"
|
|
141
|
-
"postcss",
|
|
142
|
-
"postcss
|
|
143
|
-
"cssnano",
|
|
144
|
-
"@jridgewell/gen-mapping"
|
|
167
|
+
pkg("tailwindcss"),
|
|
168
|
+
pkg("postcss"),
|
|
169
|
+
pkg("postcss-cli"),
|
|
170
|
+
pkg("@tailwindcss/postcss")
|
|
145
171
|
);
|
|
146
172
|
}
|
|
147
173
|
if (answer.websocket) {
|
|
148
|
-
dependencies.push("chokidar-cli");
|
|
174
|
+
dependencies.push(pkg("chokidar-cli"));
|
|
149
175
|
}
|
|
150
176
|
if (answer.prisma) {
|
|
151
|
-
dependencies.push("prisma", "@prisma/client");
|
|
177
|
+
dependencies.push(pkg("prisma"), pkg("@prisma/client"));
|
|
178
|
+
execSync("npm install -g prisma-client-php", { stdio: "inherit" });
|
|
152
179
|
}
|
|
153
180
|
await installDependencies(projectPath, dependencies, true);
|
|
154
181
|
if (!projectName) {
|
|
155
182
|
execSync(`npx tsc --init`, { stdio: "inherit" });
|
|
156
183
|
}
|
|
157
|
-
if (answer.tailwindcss)
|
|
158
|
-
execSync(`npx tailwindcss init -p`, { stdio: "inherit" });
|
|
159
184
|
if (answer.prisma) {
|
|
160
185
|
if (!fs.existsSync(path.join(projectPath, "prisma")))
|
|
161
186
|
execSync(`npx prisma init`, { stdio: "inherit" });
|
|
@@ -240,7 +265,7 @@ async function main() {
|
|
|
240
265
|
);
|
|
241
266
|
}
|
|
242
267
|
if (!updateAnswer.tailwindcss) {
|
|
243
|
-
const tailwindFiles = ["postcss.config.js"
|
|
268
|
+
const tailwindFiles = ["postcss.config.js"];
|
|
244
269
|
tailwindFiles.forEach((file) => {
|
|
245
270
|
const filePath = path.join(projectPath, file);
|
|
246
271
|
if (fs.existsSync(filePath)) {
|
|
@@ -252,11 +277,9 @@ async function main() {
|
|
|
252
277
|
});
|
|
253
278
|
updateUninstallDependencies.push(
|
|
254
279
|
"tailwindcss",
|
|
255
|
-
"autoprefixer",
|
|
256
280
|
"postcss",
|
|
257
281
|
"postcss-cli",
|
|
258
|
-
"
|
|
259
|
-
"@jridgewell/gen-mapping"
|
|
282
|
+
"@tailwindcss/postcss"
|
|
260
283
|
);
|
|
261
284
|
}
|
|
262
285
|
if (!updateAnswer.websocket) {
|
|
@@ -315,13 +338,11 @@ async function main() {
|
|
|
315
338
|
}
|
|
316
339
|
const projectPathModified = projectPath.replace(/\\/g, "\\");
|
|
317
340
|
const bsConfig = bsConfigUrls(projectPathModified);
|
|
318
|
-
const phpGenerateClassPath = answer.prisma ? "src/Lib/Prisma/Classes" : "";
|
|
319
341
|
const prismaPhpConfig = {
|
|
320
342
|
projectName: answer.projectName,
|
|
321
343
|
projectRootPath: projectPathModified,
|
|
322
344
|
phpEnvironment: "XAMPP",
|
|
323
345
|
phpRootPathExe: "C:\\xampp\\php\\php.exe",
|
|
324
|
-
phpGenerateClassPath,
|
|
325
346
|
bsTarget: bsConfig.bsTarget,
|
|
326
347
|
bsPathRewrite: bsConfig.bsPathRewrite,
|
|
327
348
|
backendOnly: answer.backendOnly,
|
package/dist/postcss.config.js
CHANGED
package/dist/prisma/seed.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// },
|
|
23
23
|
// ];
|
|
24
24
|
|
|
25
|
-
async function main() {
|
|
25
|
+
// async function main() {
|
|
26
26
|
// ========================================
|
|
27
27
|
// Code for PostgreSQL
|
|
28
28
|
// ----------------------------------------
|
|
@@ -63,7 +63,7 @@ async function main() {
|
|
|
63
63
|
// await prisma.user.deleteMany();
|
|
64
64
|
// await prisma.user.createMany({ data: userData });
|
|
65
65
|
// ========================================
|
|
66
|
-
}
|
|
66
|
+
// }
|
|
67
67
|
|
|
68
68
|
// main()
|
|
69
69
|
// .catch((e) => {
|
package/dist/prisma-php.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import chalk from"chalk";import{spawn}from"child_process";import fs from"fs";import path from"path";import prompts from"prompts";
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from"chalk";import{spawn}from"child_process";import fs from"fs";import path from"path";import prompts from"prompts";const args=process.argv.slice(2),readJsonFile=e=>{const o=fs.readFileSync(e,"utf8");return JSON.parse(o)},executeCommand=(e,o=[],t={})=>new Promise(((r,s)=>{const a=spawn(e,o,{stdio:"inherit",shell:!0,...t});a.on("error",(e=>{s(e)})),a.on("close",(e=>{0===e?r():s(new Error(`Process exited with code ${e}`))}))}));async function getAnswer(){const e=[{type:"toggle",name:"shouldProceed",message:`This command will update the ${chalk.blue("create-prisma-php-app")} package and overwrite all default files. ${chalk.blue("Do you want to proceed")}?`,initial:!1,active:"Yes",inactive:"No"}],o=await prompts(e,{onCancel:()=>{process.exit(0)}});return 0===Object.keys(o).length?null:o}const commandsToExecute={update:"npx php update project"};
|
|
3
3
|
const main = async () => {
|
|
4
4
|
if (args.length === 0) {
|
|
5
5
|
console.log("No command provided.");
|
|
@@ -55,46 +55,6 @@ const main = async () => {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
if (formattedCommand === commandsToExecute.generateClass) {
|
|
59
|
-
try {
|
|
60
|
-
const currentDir = process.cwd();
|
|
61
|
-
const configPath = path.join(currentDir, "prisma-php.json");
|
|
62
|
-
if (!fs.existsSync(configPath)) {
|
|
63
|
-
console.error(
|
|
64
|
-
chalk.red(
|
|
65
|
-
"The configuration file 'prisma-php.json' was not found in the current directory."
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const localSettings = readJsonFile(configPath);
|
|
71
|
-
if (!localSettings.prisma) {
|
|
72
|
-
console.error(
|
|
73
|
-
chalk.red(
|
|
74
|
-
"Install the 'Prisma PHP ORM' package by running the command 'npx php update project'."
|
|
75
|
-
)
|
|
76
|
-
);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const prismaClientPath = path.join(
|
|
80
|
-
__dirname,
|
|
81
|
-
"prisma-client-php",
|
|
82
|
-
"index.js"
|
|
83
|
-
);
|
|
84
|
-
if (!fs.existsSync(prismaClientPath)) {
|
|
85
|
-
console.error(
|
|
86
|
-
chalk.red(
|
|
87
|
-
"The 'prisma-client-php' package was not found in the current directory."
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
console.log("Executing command...\n");
|
|
93
|
-
await executeCommand("node", [prismaClientPath]);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error("Error in script execution:", error);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
58
|
};
|
|
99
59
|
main().catch((error) => {
|
|
100
60
|
console.error("Unhandled error in main function:", error);
|
|
@@ -102,15 +102,39 @@ const deleteFilesIfExist = async (filePaths: string[]): Promise<void> => {
|
|
|
102
102
|
console.error(`Error deleting ${filePath}:`, error);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
// If the file is 'request-data.json', recreate it as an empty file
|
|
107
|
+
if (filePath.endsWith("request-data.json")) {
|
|
108
|
+
try {
|
|
109
|
+
await fsPromises.writeFile(filePath, ""); // Create an empty file
|
|
110
|
+
// console.log(`Created empty ${filePath}`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error creating empty ${filePath}:`, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
105
115
|
}
|
|
106
116
|
};
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
async function deleteDirectoriesIfExist(dirPaths: string[]): Promise<void> {
|
|
119
|
+
for (const dirPath of dirPaths) {
|
|
120
|
+
try {
|
|
121
|
+
await fsPromises.rm(dirPath, { recursive: true, force: true });
|
|
122
|
+
console.log(`Deleted directory: ${dirPath}`);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`Error deleting directory (${dirPath}):`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const filesToDelete = [
|
|
109
130
|
join(__dirname, "request-data.json"),
|
|
110
131
|
join(__dirname, "class-log.json"),
|
|
111
132
|
join(__dirname, "class-imports.json"),
|
|
112
133
|
];
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
const dirsToDelete = [join(__dirname, "..", "caches")];
|
|
136
|
+
|
|
137
|
+
await deleteFilesIfExist(filesToDelete);
|
|
138
|
+
await deleteDirectoriesIfExist(dirsToDelete);
|
|
115
139
|
await updateAllClassLogs();
|
|
116
140
|
await updateComponentImports();
|
|
@@ -7,10 +7,8 @@ namespace Lib\AI;
|
|
|
7
7
|
use GuzzleHttp\Client;
|
|
8
8
|
use GuzzleHttp\Exception\RequestException;
|
|
9
9
|
use Lib\Validator;
|
|
10
|
+
use RuntimeException;
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* ChatGPTClient handles communication with the OpenAI API for generating chat responses.
|
|
13
|
-
*/
|
|
14
12
|
class ChatGPTClient
|
|
15
13
|
{
|
|
16
14
|
private Client $client;
|
|
@@ -18,9 +16,6 @@ class ChatGPTClient
|
|
|
18
16
|
private string $apiKey = '';
|
|
19
17
|
private array $cache = [];
|
|
20
18
|
|
|
21
|
-
/**
|
|
22
|
-
* Constructor initializes the Guzzle HTTP client and sets up API configuration.
|
|
23
|
-
*/
|
|
24
19
|
public function __construct(Client $client = null)
|
|
25
20
|
{
|
|
26
21
|
// Initialize the Guzzle HTTP client, allowing for dependency injection
|
|
@@ -42,9 +37,11 @@ class ChatGPTClient
|
|
|
42
37
|
protected function determineModel(array $conversationHistory): string
|
|
43
38
|
{
|
|
44
39
|
$messageCount = count($conversationHistory);
|
|
45
|
-
$totalTokens = array_reduce(
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
$totalTokens = array_reduce(
|
|
41
|
+
$conversationHistory,
|
|
42
|
+
fn($carry, $item) => $carry + str_word_count($item['content'] ?? ''),
|
|
43
|
+
0
|
|
44
|
+
);
|
|
48
45
|
|
|
49
46
|
// If the conversation is long or complex, use a model with more tokens
|
|
50
47
|
if ($totalTokens > 4000 || $messageCount > 10) {
|
|
@@ -75,13 +72,14 @@ class ChatGPTClient
|
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/**
|
|
78
|
-
* Sends a message to the OpenAI API and returns the AI's response.
|
|
75
|
+
* Sends a message to the OpenAI API and returns the AI's response as HTML.
|
|
76
|
+
*
|
|
77
|
+
* @param array $conversationHistory The conversation history array containing previous messages.
|
|
78
|
+
* @param string $userMessage The new user message to add to the conversation.
|
|
79
|
+
* @return string The AI-generated HTML response.
|
|
79
80
|
*
|
|
80
|
-
* @param array $conversationHistory The conversation history array containing previous messages.
|
|
81
|
-
* @param string $userMessage The new user message to add to the conversation.
|
|
82
|
-
* @return string The AI-generated response.
|
|
83
81
|
* @throws \InvalidArgumentException If a message in the conversation history is not valid.
|
|
84
|
-
* @throws
|
|
82
|
+
* @throws RuntimeException If the API request fails or returns an unexpected format.
|
|
85
83
|
*/
|
|
86
84
|
public function sendMessage(array $conversationHistory, string $userMessage): string
|
|
87
85
|
{
|
|
@@ -92,13 +90,20 @@ class ChatGPTClient
|
|
|
92
90
|
// Optional: Convert emojis or special patterns in the message
|
|
93
91
|
$userMessage = Validator::emojis($userMessage);
|
|
94
92
|
|
|
95
|
-
//
|
|
93
|
+
// Prepare the conversation, including a system-level instruction to return valid HTML
|
|
94
|
+
$systemInstruction = [
|
|
95
|
+
'role' => 'system',
|
|
96
|
+
'content' => 'You are ChatGPT. Please provide your response in valid HTML format.'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// Format existing history, then prepend the system message
|
|
96
100
|
$formattedHistory = $this->formatConversationHistory($conversationHistory);
|
|
101
|
+
array_unshift($formattedHistory, $systemInstruction);
|
|
97
102
|
|
|
98
|
-
//
|
|
103
|
+
// Append the new user message
|
|
99
104
|
$formattedHistory[] = ['role' => 'user', 'content' => $userMessage];
|
|
100
105
|
|
|
101
|
-
// Check cache
|
|
106
|
+
// Check cache
|
|
102
107
|
$cacheKey = md5(serialize($formattedHistory));
|
|
103
108
|
if (isset($this->cache[$cacheKey])) {
|
|
104
109
|
return $this->cache[$cacheKey];
|
|
@@ -112,107 +117,31 @@ class ChatGPTClient
|
|
|
112
117
|
$response = $this->client->request('POST', $this->apiUrl, [
|
|
113
118
|
'headers' => [
|
|
114
119
|
'Authorization' => 'Bearer ' . $this->apiKey,
|
|
115
|
-
'Content-Type'
|
|
120
|
+
'Content-Type' => 'application/json',
|
|
116
121
|
],
|
|
117
122
|
'json' => [
|
|
118
|
-
'model'
|
|
119
|
-
'messages'
|
|
120
|
-
'max_tokens'
|
|
123
|
+
'model' => $model,
|
|
124
|
+
'messages' => $formattedHistory,
|
|
125
|
+
'max_tokens' => 500,
|
|
121
126
|
],
|
|
122
127
|
]);
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
$
|
|
126
|
-
$responseContent = json_decode($responseBody, true);
|
|
129
|
+
$responseBody = $response->getBody();
|
|
130
|
+
$responseContent = json_decode((string) $responseBody, true);
|
|
127
131
|
|
|
128
132
|
// Check if response is in expected format
|
|
129
133
|
if (isset($responseContent['choices'][0]['message']['content'])) {
|
|
130
134
|
$aiMessage = $responseContent['choices'][0]['message']['content'];
|
|
135
|
+
|
|
131
136
|
// Cache the result
|
|
132
137
|
$this->cache[$cacheKey] = $aiMessage;
|
|
138
|
+
|
|
133
139
|
return $aiMessage;
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
throw new
|
|
142
|
+
throw new RuntimeException('Unexpected API response format.');
|
|
137
143
|
} catch (RequestException $e) {
|
|
138
|
-
|
|
139
|
-
throw new \RuntimeException("API request failed: " . $e->getMessage(), 0, $e);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Converts a GPT response to user-friendly HTML.
|
|
145
|
-
*
|
|
146
|
-
* @param string $gptResponse The raw response from GPT.
|
|
147
|
-
* @return string The formatted HTML.
|
|
148
|
-
*/
|
|
149
|
-
public function formatGPTResponseToHTML(string $gptResponse): string
|
|
150
|
-
{
|
|
151
|
-
try {
|
|
152
|
-
// Decode all HTML entities including numeric ones
|
|
153
|
-
$gptResponse = html_entity_decode($gptResponse, ENT_QUOTES | ENT_HTML401, 'UTF-8');
|
|
154
|
-
|
|
155
|
-
// Decode HTML special characters that might still be encoded
|
|
156
|
-
$gptResponse = htmlspecialchars_decode($gptResponse, ENT_QUOTES | ENT_HTML401);
|
|
157
|
-
|
|
158
|
-
// Handle code blocks with optional language identifiers (```csharp ... ``` or ``` ... ```)
|
|
159
|
-
$gptResponse = preg_replace_callback('/```(\w+)?\s*([\s\S]*?)\s*```/m', function ($matches) {
|
|
160
|
-
$languageClass = isset($matches[1]) ? ' class="language-' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '"' : '';
|
|
161
|
-
return '<pre><code' . $languageClass . '>' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '</code></pre>';
|
|
162
|
-
}, $gptResponse);
|
|
163
|
-
|
|
164
|
-
// Convert inline code (`code`) to <code></code> without double escaping
|
|
165
|
-
$gptResponse = preg_replace_callback('/`([^`]+)`/', function ($matches) {
|
|
166
|
-
return '<code>' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '</code>';
|
|
167
|
-
}, $gptResponse);
|
|
168
|
-
|
|
169
|
-
// Convert bold text (e.g., **text** or __text__ -> <strong>text</strong>)
|
|
170
|
-
$gptResponse = preg_replace('/(\*\*|__)(.*?)\1/', '<strong>$2</strong>', $gptResponse);
|
|
171
|
-
|
|
172
|
-
// Convert italic text (e.g., *text* or _text_ -> <em>text</em>)
|
|
173
|
-
$gptResponse = preg_replace('/(\*|_)(.*?)\1/', '<em>$2</em>', $gptResponse);
|
|
174
|
-
|
|
175
|
-
// Convert strikethrough text (e.g., ~~text~~ -> <del>text</del>)
|
|
176
|
-
$gptResponse = preg_replace('/~~(.*?)~~/s', '<del>$1</del>', $gptResponse);
|
|
177
|
-
|
|
178
|
-
// Convert Markdown links [text](url) to HTML links <a href="url">text</a>
|
|
179
|
-
$gptResponse = preg_replace_callback('/\[(.*?)\]\((https?:\/\/[^\s]+)\)/', function ($matches) {
|
|
180
|
-
return '<a href="' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '" target="_blank">' . htmlspecialchars($matches[1], ENT_QUOTES | ENT_HTML401) . '</a>';
|
|
181
|
-
}, $gptResponse);
|
|
182
|
-
|
|
183
|
-
// Auto-detect and convert raw URLs to clickable links
|
|
184
|
-
$gptResponse = preg_replace('/(?<!href="|">)(https?:\/\/[^\s]+)/', '<a href="$1" target="_blank">$1</a>', $gptResponse);
|
|
185
|
-
|
|
186
|
-
// Convert headers (e.g., # Header -> <h1>Header</h1>)
|
|
187
|
-
$gptResponse = preg_replace_callback('/^(#{1,6})\s*(.*?)$/m', function ($matches) {
|
|
188
|
-
$level = strlen($matches[1]);
|
|
189
|
-
return '<h' . $level . '>' . htmlspecialchars($matches[2], ENT_QUOTES | ENT_HTML401) . '</h' . $level . '>';
|
|
190
|
-
}, $gptResponse);
|
|
191
|
-
|
|
192
|
-
// Convert blockquotes (e.g., > quote -> <blockquote>quote</blockquote>)
|
|
193
|
-
$gptResponse = preg_replace('/^>\s*(.*?)$/m', '<blockquote>$1</blockquote>', $gptResponse);
|
|
194
|
-
|
|
195
|
-
// Convert unordered lists (e.g., - item or * item -> <ul><li>item</li></ul>)
|
|
196
|
-
$gptResponse = preg_replace_callback('/(?:^\s*[-*]\s+.*$(?:\n|$))+/m', function ($matches) {
|
|
197
|
-
return '<ul>' . preg_replace('/^\s*[-*]\s+(.*)$/m', '<li>$1</li>', $matches[0]) . '</ul>';
|
|
198
|
-
}, $gptResponse);
|
|
199
|
-
|
|
200
|
-
// Convert ordered lists (e.g., 1. item -> <ol><li>item</li></ol>)
|
|
201
|
-
$gptResponse = preg_replace_callback('/(?:^\d+\.\s+.*$(?:\n|$))+/m', function ($matches) {
|
|
202
|
-
return '<ol>' . preg_replace('/^\s*\d+\.\s+(.*)$/m', '<li>$1</li>', $matches[0]) . '</ol>';
|
|
203
|
-
}, $gptResponse);
|
|
204
|
-
|
|
205
|
-
// Convert newlines to <br> for better formatting in HTML, except within <pre> and <code> tags
|
|
206
|
-
$gptResponse = preg_replace_callback('/<(pre|code)>(.*?)<\/\1>/s', function ($matches) {
|
|
207
|
-
return $matches[0]; // Keep preformatted text as it is
|
|
208
|
-
}, $gptResponse);
|
|
209
|
-
|
|
210
|
-
// Convert remaining newlines to <br>
|
|
211
|
-
$gptResponse = nl2br($gptResponse);
|
|
212
|
-
|
|
213
|
-
return $gptResponse;
|
|
214
|
-
} catch (\Throwable) {
|
|
215
|
-
return $gptResponse;
|
|
144
|
+
throw new RuntimeException("API request failed: " . $e->getMessage(), 0, $e);
|
|
216
145
|
}
|
|
217
146
|
}
|
|
218
147
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib;
|
|
6
|
+
|
|
7
|
+
use Lib\PrismaPHPSettings;
|
|
8
|
+
|
|
9
|
+
class CacheHandler
|
|
10
|
+
{
|
|
11
|
+
public static bool $isCacheable = true; // Enable or disable caching by route
|
|
12
|
+
public static int $ttl = 0; // Time to live in seconds (0 = no action taken)
|
|
13
|
+
|
|
14
|
+
private static string $cacheDir = DOCUMENT_PATH . '/caches';
|
|
15
|
+
private static bool $cacheDirChecked = false;
|
|
16
|
+
|
|
17
|
+
private static function ensureCacheDirectoryExists(): void
|
|
18
|
+
{
|
|
19
|
+
if (self::$cacheDirChecked) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!is_dir(self::$cacheDir) && !mkdir(self::$cacheDir, 0777, true) && !is_dir(self::$cacheDir)) {
|
|
24
|
+
die("Error: Unable to create cache directory at: " . self::$cacheDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
self::$cacheDirChecked = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static function getCacheFilePath(string $uri): string
|
|
31
|
+
{
|
|
32
|
+
$requestFilesData = PrismaPHPSettings::$includeFiles;
|
|
33
|
+
$fileName = $requestFilesData[$uri]['fileName'] ?? '';
|
|
34
|
+
$isCacheable = $requestFilesData[$uri]['isCacheable'] ?? self::$isCacheable;
|
|
35
|
+
|
|
36
|
+
if (!$isCacheable || $fileName === '') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return self::$cacheDir . '/' . $fileName . '.html';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static function isExpired(string $cacheFile, int $ttlSeconds = 600): bool
|
|
44
|
+
{
|
|
45
|
+
if (!file_exists($cacheFile)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
$fileAge = time() - filemtime($cacheFile);
|
|
49
|
+
return $fileAge > $ttlSeconds;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Serve cache if available, not expired, and route is marked cacheable.
|
|
54
|
+
* We look up a route-specific TTL if defined, otherwise use a fallback.
|
|
55
|
+
*/
|
|
56
|
+
public static function serveCache(string $uri, int $defaultTtl = 600): void
|
|
57
|
+
{
|
|
58
|
+
if ($uri === '') {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
$requestFilesData = PrismaPHPSettings::$includeFiles;
|
|
63
|
+
|
|
64
|
+
// Get the route-specific TTL if set, or default to 0
|
|
65
|
+
$routeTtl = $requestFilesData[$uri]['cacheTtl'] ?? 0;
|
|
66
|
+
|
|
67
|
+
// If the route has a TTL greater than 0, use that.
|
|
68
|
+
// Otherwise (0 or not defined), use the default.
|
|
69
|
+
$ttlSeconds = ($routeTtl > 0) ? $routeTtl : $defaultTtl;
|
|
70
|
+
|
|
71
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
72
|
+
|
|
73
|
+
if ($cacheFile === '' || !file_exists($cacheFile) || self::isExpired($cacheFile, $ttlSeconds)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
echo "<!-- Cached copy generated at: " . date('Y-m-d H:i:s', filemtime($cacheFile)) . " -->\n";
|
|
78
|
+
readfile($cacheFile);
|
|
79
|
+
exit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static function saveCache(string $uri, string $content, bool $useLock = true): void
|
|
83
|
+
{
|
|
84
|
+
if ($uri === '') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
self::ensureCacheDirectoryExists();
|
|
88
|
+
|
|
89
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
90
|
+
if ($cacheFile === '') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
$flags = $useLock ? LOCK_EX : 0;
|
|
95
|
+
$written = @file_put_contents($cacheFile, $content, $flags);
|
|
96
|
+
|
|
97
|
+
if ($written === false) {
|
|
98
|
+
die("Error: Failed to write cache file: $cacheFile");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static function resetCache(?string $uri = null): void
|
|
103
|
+
{
|
|
104
|
+
self::ensureCacheDirectoryExists();
|
|
105
|
+
|
|
106
|
+
if ($uri !== null) {
|
|
107
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
108
|
+
if ($cacheFile !== '' && file_exists($cacheFile)) {
|
|
109
|
+
unlink($cacheFile);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$files = glob(self::$cacheDir . '/*.html') ?: [];
|
|
115
|
+
foreach ($files as $file) {
|
|
116
|
+
if (is_file($file)) {
|
|
117
|
+
unlink($file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|