create-prisma-php-app 4.0.0-alpha.5 → 4.0.0-alpha.50

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.
Files changed (60) hide show
  1. package/dist/.htaccess +54 -41
  2. package/dist/bootstrap.php +143 -98
  3. package/dist/index.js +819 -343
  4. package/dist/settings/auto-swagger-docs.ts +196 -95
  5. package/dist/settings/bs-config.ts +53 -58
  6. package/dist/settings/files-list.json +1 -1
  7. package/dist/settings/project-name.ts +2 -0
  8. package/dist/settings/restart-mcp.ts +58 -0
  9. package/dist/settings/restart-websocket.ts +51 -45
  10. package/dist/settings/utils.ts +240 -0
  11. package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
  12. package/dist/src/Lib/Auth/Auth.php +544 -0
  13. package/dist/src/Lib/Auth/AuthConfig.php +89 -0
  14. package/dist/src/Lib/CacheHandler.php +121 -0
  15. package/dist/src/Lib/ErrorHandler.php +322 -0
  16. package/dist/src/Lib/FileManager/UploadFile.php +383 -0
  17. package/dist/src/Lib/Headers/Boom.php +192 -0
  18. package/dist/src/Lib/IncludeTracker.php +59 -0
  19. package/dist/src/Lib/MCP/WeatherTools.php +104 -0
  20. package/dist/src/Lib/MCP/mcp-server.php +80 -0
  21. package/dist/src/Lib/MainLayout.php +230 -0
  22. package/dist/src/Lib/Middleware/AuthMiddleware.php +157 -0
  23. package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
  24. package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
  25. package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
  26. package/dist/src/Lib/PHPX/Fragment.php +32 -0
  27. package/dist/src/Lib/PHPX/IPHPX.php +22 -0
  28. package/dist/src/Lib/PHPX/PHPX.php +287 -0
  29. package/dist/src/Lib/PHPX/TemplateCompiler.php +663 -0
  30. package/dist/src/Lib/PHPX/TwMerge.php +346 -0
  31. package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
  32. package/dist/src/Lib/PartialRenderer.php +40 -0
  33. package/dist/src/Lib/PrismaPHPSettings.php +181 -0
  34. package/dist/src/Lib/Request.php +479 -0
  35. package/dist/src/Lib/Security/RateLimiter.php +33 -0
  36. package/dist/src/Lib/Set.php +102 -0
  37. package/dist/src/Lib/StateManager.php +127 -0
  38. package/dist/src/Lib/Validator.php +752 -0
  39. package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
  40. package/dist/src/Lib/Websocket/websocket-server.php +118 -0
  41. package/dist/src/app/error.php +1 -1
  42. package/dist/src/app/index.php +22 -5
  43. package/dist/src/app/js/index.js +1 -1
  44. package/dist/src/app/layout.php +2 -2
  45. package/package.json +1 -1
  46. package/dist/settings/restart-websocket.bat +0 -28
  47. package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
  48. package/dist/websocket-server.php +0 -22
  49. package/vendor/autoload.php +0 -25
  50. package/vendor/composer/ClassLoader.php +0 -579
  51. package/vendor/composer/InstalledVersions.php +0 -359
  52. package/vendor/composer/LICENSE +0 -21
  53. package/vendor/composer/autoload_classmap.php +0 -10
  54. package/vendor/composer/autoload_namespaces.php +0 -9
  55. package/vendor/composer/autoload_psr4.php +0 -10
  56. package/vendor/composer/autoload_real.php +0 -38
  57. package/vendor/composer/autoload_static.php +0 -25
  58. package/vendor/composer/installed.json +0 -825
  59. package/vendor/composer/installed.php +0 -132
  60. package/vendor/composer/platform_check.php +0 -26
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";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];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 o=c.replace(/(?<!:)(\/\/+)/g,"/"),i=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${i.startsWith("/")?i.substring(1):i}/`}}}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","tailwind:build":"postcss src/app/css/tailwind.css -o src/app/css/styles.css"},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 o={...n.scripts};o.browserSync="tsx settings/bs-config.ts",o["browserSync:build"]="tsx settings/build.ts",o.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,o.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,n.scripts=o,n.type="module",fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e){const s=path.join(e,"composer.json");if(checkExcludeFiles(s))return;let t;if(fs.existsSync(s)){{const e=fs.readFileSync(s,"utf8");t=JSON.parse(e)}t.autoload={"psr-4":{"":"src/"}},t.version="1.0.0",fs.writeFileSync(s,JSON.stringify(t,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 generateHexEncodedKey(e=16){return randomBytes(e).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\\websocket"))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")||s.includes("websocket-server.php")))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest: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 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/morphdom-umd.min.js"><\/script>\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),e=e.replace("</head>",`${c}\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:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.websocket&&t.push({src:"/websocket-server.php",dest:"/websocket-server.php"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];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 o=fs.readFileSync(n,"utf8");fs.writeFileSync(c,o,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c=generateAuthSecret(),o=generateHexEncodedKey(),i=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# PHPMailer SMTP configuration (uncomment and set as needed)\n# SMTP_HOST="smtp.gmail.com" # Your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username\n# SMTP_PASSWORD="123456" # Your SMTP password\n# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port\n# SMTP_ENCRYPTION="ssl" # ssl or tls\n# MAIL_FROM="john.doe@gmail.com" # Sender email address\n# MAIL_FROM_NAME="John Doe" # Sender name\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"`;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${i}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,i)}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 o=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:o.swaggerDocs??e.swaggerDocs??!1,tailwindcss:o.tailwindcss??e.tailwindcss??!1,websocket:o.websocket??e.websocket??!1,prisma:o.prisma??e.prisma??!1,docker:o.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){s.forEach((e=>{}));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let 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,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","src/app/js/index.js","src/app/css/tailwind.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","docker-compose.yml","Dockerfile"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),c=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let n=`http://localhost/${c}`;n=n.endsWith("/")?n.slice(0,-1):n;const i=n.replace(/(?<!:)(\/\/+)/g,"/"),o=c.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 c=JSON.parse(fs.readFileSync(t,"utf8"));c.scripts={...c.scripts,projectName:"tsx settings/project-name.ts"};let n=[];if(s.tailwindcss&&(c.scripts={...c.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch","tailwind:build":"postcss src/app/css/tailwind.css -o src/app/css/styles.css"},n.push("tailwind")),s.websocket&&(c.scripts={...c.scripts,websocket:"tsx settings/restart-websocket.ts"},n.push("websocket")),s.mcp&&(c.scripts={...c.scripts,mcp:"tsx settings/restart-mcp.ts"},n.push("mcp")),s.docker&&(c.scripts={...c.scripts,docker:"docker-compose up"},n.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";c.scripts={...c.scripts,"create-swagger-docs":e}}let i={...c.scripts};i.browserSync="tsx settings/bs-config.ts",i["browserSync:build"]="tsx settings/build.ts",i.dev=`npm-run-all projectName -p browserSync ${n.join(" ")}`,i.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,c.scripts=i,c.type="module",fs.writeFileSync(t,JSON.stringify(c,null,2))}async function updateComposerJson(e){checkExcludeFiles(path.join(e,"composer.json"))}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let c=fs.readFileSync(t,"utf8");c+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,c,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const c=fs.existsSync(e),n=c&&fs.statSync(e);if(c&&n&&n.isDirectory()){const c=s.toLowerCase();if(!t.websocket&&c.includes("src\\lib\\websocket"))return;if(!t.mcp&&c.includes("src\\lib\\mcp"))return;if(t.backendOnly&&c.includes("src\\app\\js")||t.backendOnly&&c.includes("src\\app\\css")||t.backendOnly&&c.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&c.includes("src\\app\\swagger-docs"))return;const n=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(n))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((c=>{copyRecursiveSync(path.join(e,c),path.join(s,c),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.mcp&&s.includes("restart-mcp.ts"))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:c})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,c),t)}))}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),c="";s.backendOnly||(s.tailwindcss||(c='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),c+='\n <script src="<?= Request::baseUrl; ?>/js/morphdom-umd.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let n="";s.backendOnly||(n=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${c}`:c),e=e.replace("</head>",`${n}\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:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"});const c=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&c.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const c=path.join(__dirname,s),n=path.join(e,t);if(checkExcludeFiles(n))return;const i=fs.readFileSync(c,"utf8");fs.writeFileSync(n,i,{flag:"w"})})),await executeCopy(e,c,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const n=generateAuthSecret(),i=generateHexEncodedKey(),o=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${n}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# PHPMailer SMTP configuration (uncomment and set as needed)\n# SMTP_HOST="smtp.gmail.com" # Your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username\n# SMTP_PASSWORD="123456" # Your SMTP password\n# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port\n# SMTP_ENCRYPTION="ssl" # ssl or tls\n# MAIL_FROM="john.doe@gmail.com" # Sender email address\n# MAIL_FROM_NAME="John Doe" # Sender name\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${i}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"`;if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${o}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,o)}async function getAnswer(e={}){if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const c={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,docker:t.features.docker??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1},n=process.argv.slice(2);return n.includes("--backend-only")&&(c.backendOnly=!0),n.includes("--swagger-docs")&&(c.swaggerDocs=!0),n.includes("--tailwindcss")&&(c.tailwindcss=!0),n.includes("--websocket")&&(c.websocket=!0),n.includes("--mcp")&&(c.mcp=!0),n.includes("--prisma")&&(c.prisma=!0),n.includes("--docker")&&(c.docker=!0),c}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1},c=process.argv.slice(2);return c.includes("--backend-only")&&(t.backendOnly=!0),c.includes("--swagger-docs")&&(t.swaggerDocs=!0),c.includes("--tailwindcss")&&(t.tailwindcss=!0),c.includes("--websocket")&&(t.websocket=!0),c.includes("--mcp")&&(t.mcp=!0),c.includes("--prisma")&&(t.prisma=!0),c.includes("--docker")&&(t.docker=!0),t}}const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{process.exit(0)},c=await prompts(s,{onCancel:t}),n=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||n.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||n.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||n.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||n.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||n.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||n.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||n.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||n.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||n.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||n.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||n.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(n,{onCancel:t});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,mcp:i.mcp??e.mcp??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const c=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(c,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){s.forEach((e=>{}));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let c="";e.on("data",(e=>c+=e)),e.on("end",(()=>{try{const e=JSON.parse(c);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),c=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>c[e])return 1;if(t[e]<c[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
3
3
  /**
4
4
  * Install dependencies in the specified directory.
5
5
  * @param {string} baseDir - The base directory where to install the dependencies.
@@ -7,380 +7,856 @@ import{execSync}from"child_process";import fs from"fs";import{fileURLToPath}from
7
7
  * @param {boolean} [isDev=false] - Whether to install the dependencies as devDependencies.
8
8
  */
9
9
  async function installNpmDependencies(baseDir, dependencies, isDev = false) {
10
+ if (!fs.existsSync(path.join(baseDir, "package.json"))) {
10
11
  console.log("Initializing new Node.js project...");
11
- // Initialize a package.json if it doesn't exist
12
- if (!fs.existsSync(path.join(baseDir, "package.json"))) {
13
- execSync("npm init -y", {
14
- stdio: "inherit",
15
- cwd: baseDir,
16
- });
17
- }
18
- // Log the dependencies being installed
19
- console.log(`${isDev ? "Installing development dependencies" : "Installing dependencies"}:`);
20
- dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
21
- // Prepare the npm install command with the appropriate flag for dev dependencies
22
- const npmInstallCommand = `npm install ${isDev ? "--save-dev" : ""} ${dependencies.join(" ")}`;
23
- // Execute the npm install command
24
- execSync(npmInstallCommand, {
25
- stdio: "inherit",
26
- cwd: baseDir,
12
+ } else {
13
+ console.log("Updating existing Node.js project...");
14
+ }
15
+ // Initialize a package.json if it doesn't exist
16
+ if (!fs.existsSync(path.join(baseDir, "package.json"))) {
17
+ execSync("npm init -y", {
18
+ stdio: "inherit",
19
+ cwd: baseDir,
27
20
  });
21
+ }
22
+ // Log the dependencies being installed
23
+ console.log(
24
+ `${
25
+ isDev ? "Installing development dependencies" : "Installing dependencies"
26
+ }:`
27
+ );
28
+ dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
29
+ // Prepare the npm install command with the appropriate flag for dev dependencies
30
+ const npmInstallCommand = `npm install ${
31
+ isDev ? "--save-dev" : ""
32
+ } ${dependencies.join(" ")}`;
33
+ // Execute the npm install command
34
+ execSync(npmInstallCommand, {
35
+ stdio: "inherit",
36
+ cwd: baseDir,
37
+ });
28
38
  }
29
- async function installComposerDependencies(baseDir, dependencies) {
30
- console.log("Initializing new Composer project...");
31
- // Initialize a composer.json if it doesn't exist
32
- if (!fs.existsSync(path.join(baseDir, "composer.json"))) {
33
- execSync(`composer init -n --name="tsnc/prisma-php-app" --require="php:^8.2"`, {
34
- stdio: "inherit",
35
- cwd: baseDir,
36
- });
39
+ function getComposerCmd() {
40
+ try {
41
+ execSync("composer --version", { stdio: "ignore" });
42
+ return { cmd: "composer", baseArgs: [] };
43
+ } catch {
44
+ return {
45
+ cmd: "C:\\xampp\\php\\php.exe",
46
+ baseArgs: ["C:\\ProgramData\\ComposerSetup\\bin\\composer.phar"],
47
+ };
48
+ }
49
+ }
50
+ export async function installComposerDependencies(baseDir, dependencies) {
51
+ const { cmd, baseArgs } = getComposerCmd();
52
+ const composerJsonPath = path.join(baseDir, "composer.json");
53
+ const existsAlready = fs.existsSync(composerJsonPath);
54
+ console.log(
55
+ chalk.green(
56
+ `Composer project initialization: ${
57
+ existsAlready ? "Updating existing project…" : "Setting up new project…"
58
+ }`
59
+ )
60
+ );
61
+ /* ------------------------------------------------------------------ */
62
+ /* 1. Try composer init (quietly fall back if it fails) */
63
+ /* ------------------------------------------------------------------ */
64
+ if (!existsAlready) {
65
+ const initArgs = [
66
+ ...baseArgs,
67
+ "init",
68
+ "--no-interaction",
69
+ "--name",
70
+ "tsnc/prisma-php-app",
71
+ "--require",
72
+ "php:^8.2",
73
+ "--type",
74
+ "project",
75
+ "--version",
76
+ "1.0.0",
77
+ ];
78
+ const res = spawnSync(cmd, initArgs, { cwd: baseDir });
79
+ if (res.status !== 0) {
80
+ // Silent fallback: no logs, just write a minimal composer.json
81
+ fs.writeFileSync(
82
+ composerJsonPath,
83
+ JSON.stringify(
84
+ {
85
+ name: "tsnc/prisma-php-app",
86
+ type: "project",
87
+ version: "1.0.0",
88
+ require: { php: "^8.2" },
89
+ autoload: { "psr-4": { "": "src/" } },
90
+ },
91
+ null,
92
+ 2
93
+ )
94
+ );
37
95
  }
38
- // Log the dependencies being installed
96
+ }
97
+ /* 2. Ensure PSR-4 autoload entry ---------------------------------- */
98
+ const json = JSON.parse(fs.readFileSync(composerJsonPath, "utf8"));
99
+ json.autoload ??= {};
100
+ json.autoload["psr-4"] ??= {};
101
+ json.autoload["psr-4"][""] ??= "src/";
102
+ fs.writeFileSync(composerJsonPath, JSON.stringify(json, null, 2));
103
+ /* 3. Install dependencies ----------------------------------------- */
104
+ if (dependencies.length) {
39
105
  console.log("Installing Composer dependencies:");
40
- dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
41
- // Prepare the composer require command
42
- const composerRequireCommand = `C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar require ${dependencies.join(" ")}`;
43
- // Execute the composer require command
44
- execSync(composerRequireCommand, {
45
- stdio: "inherit",
46
- cwd: baseDir,
47
- });
106
+ dependencies.forEach((d) => console.log(`- ${chalk.blue(d)}`));
107
+ execSync(
108
+ `${cmd} ${[
109
+ ...baseArgs,
110
+ "require",
111
+ "--no-interaction",
112
+ ...dependencies,
113
+ ].join(" ")}`,
114
+ { stdio: "inherit", cwd: baseDir }
115
+ );
116
+ }
117
+ /* 4. Refresh lock when updating ----------------------------------- */
118
+ if (existsAlready) {
119
+ execSync(
120
+ `${cmd} ${[
121
+ ...baseArgs,
122
+ "update",
123
+ "--lock",
124
+ "--no-install",
125
+ "--no-interaction",
126
+ ].join(" ")}`,
127
+ { stdio: "inherit", cwd: baseDir }
128
+ );
129
+ }
130
+ /* 5. Regenerate autoloader ---------------------------------------- */
131
+ execSync(`${cmd} ${[...baseArgs, "dump-autoload", "--quiet"].join(" ")}`, {
132
+ stdio: "inherit",
133
+ cwd: baseDir,
134
+ });
48
135
  }
49
136
  const npmPinnedVersions = {
50
- "@tailwindcss/postcss": "^4.1.11",
51
- "@types/browser-sync": "^2.29.0",
52
- "@types/node": "^24.0.7",
53
- "@types/prompts": "^2.4.9",
54
- "browser-sync": "^3.0.4",
55
- chalk: "^5.4.1",
56
- "chokidar-cli": "^3.0.0",
57
- cssnano: "^7.0.7",
58
- "http-proxy-middleware": "^3.0.5",
59
- "npm-run-all": "^4.1.5",
60
- "php-parser": "^3.2.4",
61
- postcss: "^8.5.6",
62
- "postcss-cli": "^11.0.1",
63
- prompts: "^2.4.2",
64
- tailwindcss: "^4.1.11",
65
- tsx: "^4.20.3",
66
- typescript: "^5.8.3",
137
+ "@tailwindcss/postcss": "^4.1.11",
138
+ "@types/browser-sync": "^2.29.0",
139
+ "@types/node": "^24.2.1",
140
+ "@types/prompts": "^2.4.9",
141
+ "browser-sync": "^3.0.4",
142
+ chalk: "^5.5.0",
143
+ cssnano: "^7.1.0",
144
+ "http-proxy-middleware": "^3.0.5",
145
+ "npm-run-all": "^4.1.5",
146
+ "php-parser": "^3.2.5",
147
+ postcss: "^8.5.6",
148
+ "postcss-cli": "^11.0.1",
149
+ prompts: "^2.4.2",
150
+ tailwindcss: "^4.1.11",
151
+ tsx: "^4.20.3",
152
+ typescript: "^5.9.2",
67
153
  };
68
154
  function npmPkg(name) {
69
- return npmPinnedVersions[name] ? `${name}@${npmPinnedVersions[name]}` : name;
155
+ return npmPinnedVersions[name] ? `${name}@${npmPinnedVersions[name]}` : name;
70
156
  }
71
157
  const composerPinnedVersions = {
72
- "vlucas/phpdotenv": "^5.6.2",
73
- "firebase/php-jwt": "^6.11.1",
74
- "phpmailer/phpmailer": "^6.10.0",
75
- "guzzlehttp/guzzle": "^7.9.3",
76
- "ezyang/htmlpurifier": "^4.18.0",
77
- "symfony/uid": "^7.2.0",
78
- "brick/math": "^0.13.1",
79
- "cboden/ratchet": "^0.4.4",
80
- "tsnc/prisma-php": "^1.0.0",
158
+ "vlucas/phpdotenv": "^5.6.2",
159
+ "firebase/php-jwt": "^6.11.1",
160
+ "phpmailer/phpmailer": "^6.10.0",
161
+ "guzzlehttp/guzzle": "^7.9.3",
162
+ "ezyang/htmlpurifier": "^4.18.0",
163
+ "symfony/uid": "^7.2.0",
164
+ "brick/math": "^0.13.1",
165
+ "cboden/ratchet": "^0.4.4",
166
+ "tsnc/prisma-php": "^1.0.0",
167
+ "php-mcp/server": "3.3.0",
81
168
  };
82
169
  function composerPkg(name) {
83
- return composerPinnedVersions[name]
84
- ? `${name}:${composerPinnedVersions[name]}`
85
- : name;
170
+ return composerPinnedVersions[name]
171
+ ? `${name}:${composerPinnedVersions[name]}`
172
+ : name;
86
173
  }
87
- async function main() {
174
+ async function setupStarterKit(baseDir, answer) {
175
+ if (!answer.starterKit) return;
176
+ let starterKit = null;
177
+ // Check if it's a built-in starter kit
178
+ if (STARTER_KITS[answer.starterKit]) {
179
+ starterKit = STARTER_KITS[answer.starterKit];
180
+ }
181
+ // Handle custom starter kit URL
182
+ else if (answer.starterKitSource) {
183
+ starterKit = {
184
+ id: answer.starterKit,
185
+ name: `Custom Starter Kit (${answer.starterKit})`,
186
+ description: "Custom starter kit from external source",
187
+ features: {}, // Will be determined from the downloaded kit
188
+ requiredFiles: [],
189
+ source: {
190
+ type: "git", // Assume git for now, could be enhanced
191
+ url: answer.starterKitSource,
192
+ },
193
+ };
194
+ }
195
+ if (!starterKit) {
196
+ console.warn(
197
+ chalk.yellow(`Starter kit '${answer.starterKit}' not found. Skipping...`)
198
+ );
199
+ return;
200
+ }
201
+ console.log(chalk.green(`Setting up ${starterKit.name}...`));
202
+ // If it's a custom starter kit with source, clone it directly to the target directory
203
+ if (starterKit.source) {
88
204
  try {
89
- const args = process.argv.slice(2);
90
- let projectName = args[0];
91
- let answer = null;
92
- if (projectName) {
93
- let useBackendOnly = args.includes("--backend-only");
94
- let useSwaggerDocs = args.includes("--swagger-docs");
95
- let useTailwind = args.includes("--tailwindcss");
96
- let useWebsocket = args.includes("--websocket");
97
- let usePrisma = args.includes("--prisma");
98
- let useDocker = args.includes("--docker");
99
- const predefinedAnswers = {
100
- projectName,
101
- backendOnly: useBackendOnly,
102
- swaggerDocs: useSwaggerDocs,
103
- tailwindcss: useTailwind,
104
- websocket: useWebsocket,
105
- prisma: usePrisma,
106
- docker: useDocker,
107
- };
108
- answer = await getAnswer(predefinedAnswers);
109
- if (answer === null) {
110
- console.log(chalk.red("Installation cancelled."));
111
- return;
112
- }
113
- const currentDir = process.cwd();
114
- const configPath = path.join(currentDir, "prisma-php.json");
115
- if (fs.existsSync(configPath)) {
116
- const localSettings = readJsonFile(configPath);
117
- let excludeFiles = [];
118
- localSettings.excludeFiles?.map((file) => {
119
- const filePath = path.join(currentDir, file);
120
- if (fs.existsSync(filePath))
121
- excludeFiles.push(filePath.replace(/\\/g, "/"));
122
- });
123
- updateAnswer = {
124
- projectName,
125
- backendOnly: answer?.backendOnly ?? false,
126
- swaggerDocs: answer?.swaggerDocs ?? false,
127
- tailwindcss: answer?.tailwindcss ?? false,
128
- websocket: answer?.websocket ?? false,
129
- prisma: answer?.prisma ?? false,
130
- docker: answer?.docker ?? false,
131
- isUpdate: true,
132
- excludeFiles: localSettings.excludeFiles ?? [],
133
- excludeFilePath: excludeFiles ?? [],
134
- filePath: currentDir,
135
- };
136
- }
205
+ // Clone directly to the target directory
206
+ const cloneCommand = starterKit.source.branch
207
+ ? `git clone -b ${starterKit.source.branch} --depth 1 ${starterKit.source.url} ${baseDir}`
208
+ : `git clone --depth 1 ${starterKit.source.url} ${baseDir}`;
209
+ execSync(cloneCommand, { stdio: "inherit" });
210
+ // Remove .git directory
211
+ const gitDir = path.join(baseDir, ".git");
212
+ if (fs.existsSync(gitDir)) {
213
+ fs.rmSync(gitDir, { recursive: true, force: true });
214
+ }
215
+ console.log(chalk.blue("Starter kit cloned successfully!"));
216
+ // Update the project name in the existing prisma-php.json
217
+ const configPath = path.join(baseDir, "prisma-php.json");
218
+ if (fs.existsSync(configPath)) {
219
+ try {
220
+ const existingConfig = JSON.parse(
221
+ fs.readFileSync(configPath, "utf8")
222
+ );
223
+ // Only update project-specific fields, preserve everything else
224
+ const projectPathModified = baseDir.replace(/\\/g, "\\");
225
+ const bsConfig = bsConfigUrls(projectPathModified);
226
+ existingConfig.projectName = answer.projectName;
227
+ existingConfig.projectRootPath = projectPathModified;
228
+ existingConfig.bsTarget = bsConfig.bsTarget;
229
+ existingConfig.bsPathRewrite = bsConfig.bsPathRewrite;
230
+ // Update version to latest
231
+ const latestVersion = await fetchPackageVersion(
232
+ "create-prisma-php-app"
233
+ );
234
+ existingConfig.version = latestVersion;
235
+ fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
236
+ console.log(
237
+ chalk.green("Updated prisma-php.json with new project details")
238
+ );
239
+ } catch (error) {
240
+ console.warn(
241
+ chalk.yellow(
242
+ "Failed to update prisma-php.json, will create new one"
243
+ )
244
+ );
137
245
  }
138
- else {
139
- answer = await getAnswer();
246
+ }
247
+ } catch (error) {
248
+ console.error(chalk.red(`Failed to setup starter kit: ${error}`));
249
+ throw error;
250
+ }
251
+ }
252
+ // Run custom setup if defined
253
+ if (starterKit.customSetup) {
254
+ await starterKit.customSetup(baseDir, answer);
255
+ }
256
+ console.log(chalk.green(`✓ ${starterKit.name} setup complete!`));
257
+ }
258
+ function showStarterKits() {
259
+ console.log(chalk.blue("\n🚀 Available Starter Kits:\n"));
260
+ Object.values(STARTER_KITS).forEach((kit) => {
261
+ const isCustom = kit.source ? " (Custom)" : " (Built-in)";
262
+ console.log(chalk.green(` ${kit.id}${chalk.gray(isCustom)}`));
263
+ console.log(` ${kit.name}`);
264
+ console.log(chalk.gray(` ${kit.description}`));
265
+ if (kit.source) {
266
+ console.log(chalk.cyan(` Source: ${kit.source.url}`));
267
+ }
268
+ const features = Object.entries(kit.features)
269
+ .filter(([, value]) => value === true)
270
+ .map(([key]) => key)
271
+ .join(", ");
272
+ if (features) {
273
+ console.log(chalk.magenta(` Features: ${features}`));
274
+ }
275
+ console.log();
276
+ });
277
+ console.log(chalk.yellow("Usage:"));
278
+ console.log(` npx create-prisma-php-app my-project --starter-kit=basic`);
279
+ console.log(
280
+ ` npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo`
281
+ );
282
+ console.log();
283
+ }
284
+ async function main() {
285
+ try {
286
+ const args = process.argv.slice(2);
287
+ let projectName = args[0];
288
+ // Parse starter kit arguments
289
+ const starterKitArg = args.find((arg) => arg.startsWith("--starter-kit="));
290
+ const starterKitFromArgs = starterKitArg?.split("=")[1];
291
+ // Parse custom starter kit source
292
+ const starterKitSourceArg = args.find((arg) =>
293
+ arg.startsWith("--starter-kit-source=")
294
+ );
295
+ const starterKitSource = starterKitSourceArg?.split("=")[1];
296
+ // Show help
297
+ if (args.includes("--list-starter-kits")) {
298
+ showStarterKits();
299
+ return;
300
+ }
301
+ let answer = null;
302
+ let isStarterKitProject = false;
303
+ if (projectName) {
304
+ const currentDir = process.cwd();
305
+ const configPath = path.join(currentDir, "prisma-php.json");
306
+ // Check if it's a starter kit project
307
+ if (starterKitFromArgs && starterKitSource) {
308
+ isStarterKitProject = true;
309
+ const predefinedAnswers = {
310
+ projectName,
311
+ starterKit: starterKitFromArgs,
312
+ starterKitSource: starterKitSource,
313
+ backendOnly: args.includes("--backend-only"),
314
+ swaggerDocs: args.includes("--swagger-docs"),
315
+ tailwindcss: args.includes("--tailwindcss"),
316
+ websocket: args.includes("--websocket"),
317
+ mcp: args.includes("--mcp"),
318
+ prisma: args.includes("--prisma"),
319
+ docker: args.includes("--docker"),
320
+ };
321
+ answer = await getAnswer(predefinedAnswers);
322
+ } else if (fs.existsSync(configPath)) {
323
+ // It's an update - read existing settings
324
+ const localSettings = readJsonFile(configPath);
325
+ let excludeFiles = [];
326
+ localSettings.excludeFiles?.map((file) => {
327
+ const filePath = path.join(currentDir, file);
328
+ if (fs.existsSync(filePath))
329
+ excludeFiles.push(filePath.replace(/\\/g, "/"));
330
+ });
331
+ // Set updateAnswer with OLD settings initially (for checkExcludeFiles function)
332
+ updateAnswer = {
333
+ projectName,
334
+ backendOnly: localSettings.backendOnly,
335
+ swaggerDocs: localSettings.swaggerDocs,
336
+ tailwindcss: localSettings.tailwindcss,
337
+ websocket: localSettings.websocket,
338
+ mcp: localSettings.mcp,
339
+ prisma: localSettings.prisma,
340
+ docker: localSettings.docker,
341
+ isUpdate: true,
342
+ excludeFiles: localSettings.excludeFiles ?? [],
343
+ excludeFilePath: excludeFiles ?? [],
344
+ filePath: currentDir,
345
+ };
346
+ // For updates, use existing settings but allow CLI overrides
347
+ const predefinedAnswers = {
348
+ projectName,
349
+ backendOnly:
350
+ args.includes("--backend-only") || localSettings.backendOnly,
351
+ swaggerDocs:
352
+ args.includes("--swagger-docs") || localSettings.swaggerDocs,
353
+ tailwindcss:
354
+ args.includes("--tailwindcss") || localSettings.tailwindcss,
355
+ websocket: args.includes("--websocket") || localSettings.websocket,
356
+ prisma: args.includes("--prisma") || localSettings.prisma,
357
+ docker: args.includes("--docker") || localSettings.docker,
358
+ mcp: args.includes("--mcp") || localSettings.mcp,
359
+ };
360
+ answer = await getAnswer(predefinedAnswers);
361
+ // IMPORTANT: Update updateAnswer with the NEW answer after getting user input
362
+ if (answer !== null) {
363
+ updateAnswer = {
364
+ projectName,
365
+ backendOnly: answer.backendOnly,
366
+ swaggerDocs: answer.swaggerDocs,
367
+ tailwindcss: answer.tailwindcss,
368
+ websocket: answer.websocket,
369
+ mcp: answer.mcp,
370
+ prisma: answer.prisma,
371
+ docker: answer.docker,
372
+ isUpdate: true,
373
+ excludeFiles: localSettings.excludeFiles ?? [],
374
+ excludeFilePath: excludeFiles ?? [],
375
+ filePath: currentDir,
376
+ };
140
377
  }
141
- if (answer === null) {
142
- console.warn(chalk.red("Installation cancelled."));
143
- return;
378
+ } else {
379
+ // New project
380
+ const predefinedAnswers = {
381
+ projectName,
382
+ starterKit: starterKitFromArgs,
383
+ starterKitSource: starterKitSource,
384
+ backendOnly: args.includes("--backend-only"),
385
+ swaggerDocs: args.includes("--swagger-docs"),
386
+ tailwindcss: args.includes("--tailwindcss"),
387
+ websocket: args.includes("--websocket"),
388
+ mcp: args.includes("--mcp"),
389
+ prisma: args.includes("--prisma"),
390
+ docker: args.includes("--docker"),
391
+ };
392
+ answer = await getAnswer(predefinedAnswers);
393
+ }
394
+ if (answer === null) {
395
+ console.log(chalk.red("Installation cancelled."));
396
+ return;
397
+ }
398
+ } else {
399
+ // Interactive mode
400
+ answer = await getAnswer();
401
+ }
402
+ if (answer === null) {
403
+ console.warn(chalk.red("Installation cancelled."));
404
+ return;
405
+ }
406
+ const latestVersionOfCreatePrismaPhpApp = await fetchPackageVersion(
407
+ "create-prisma-php-app"
408
+ );
409
+ const isCreatePrismaPhpAppInstalled = getInstalledPackageVersion(
410
+ "create-prisma-php-app"
411
+ );
412
+ if (isCreatePrismaPhpAppInstalled) {
413
+ if (
414
+ compareVersions(
415
+ isCreatePrismaPhpAppInstalled,
416
+ latestVersionOfCreatePrismaPhpApp
417
+ ) === -1
418
+ ) {
419
+ execSync("npm uninstall -g create-prisma-php-app", {
420
+ stdio: "inherit",
421
+ });
422
+ execSync("npm install -g create-prisma-php-app", {
423
+ stdio: "inherit",
424
+ });
425
+ }
426
+ } else {
427
+ execSync("npm install -g create-prisma-php-app", { stdio: "inherit" });
428
+ }
429
+ // Create the project directory
430
+ const currentDir = process.cwd();
431
+ let projectPath;
432
+ if (projectName) {
433
+ if (isStarterKitProject) {
434
+ // For starter kit projects, create directory first
435
+ const projectNamePath = path.join(currentDir, projectName);
436
+ if (!fs.existsSync(projectNamePath)) {
437
+ fs.mkdirSync(projectNamePath, { recursive: true });
144
438
  }
145
- const latestVersionOfCreatePrismaPhpApp = await fetchPackageVersion("create-prisma-php-app");
146
- const isCreatePrismaPhpAppInstalled = getInstalledPackageVersion("create-prisma-php-app");
147
- if (isCreatePrismaPhpAppInstalled) {
148
- if (compareVersions(isCreatePrismaPhpAppInstalled, latestVersionOfCreatePrismaPhpApp) === -1) {
149
- execSync("npm uninstall -g create-prisma-php-app", {
150
- stdio: "inherit",
151
- });
152
- execSync("npm install -g create-prisma-php-app", {
153
- stdio: "inherit",
154
- });
155
- }
439
+ projectPath = projectNamePath;
440
+ // Clone the starter kit first
441
+ await setupStarterKit(projectPath, answer);
442
+ // Change to project directory
443
+ process.chdir(projectPath);
444
+ // Now check if it has prisma-php.json and treat as update
445
+ const configPath = path.join(projectPath, "prisma-php.json");
446
+ if (fs.existsSync(configPath)) {
447
+ // Read the existing config and merge with CLI overrides
448
+ const existingConfig = JSON.parse(
449
+ fs.readFileSync(configPath, "utf8")
450
+ );
451
+ // Override with CLI arguments if provided
452
+ if (args.includes("--backend-only"))
453
+ existingConfig.backendOnly = true;
454
+ if (args.includes("--swagger-docs"))
455
+ existingConfig.swaggerDocs = true;
456
+ if (args.includes("--tailwindcss")) existingConfig.tailwindcss = true;
457
+ if (args.includes("--websocket")) existingConfig.websocket = true;
458
+ if (args.includes("--mcp")) existingConfig.mcp = true;
459
+ if (args.includes("--prisma")) existingConfig.prisma = true;
460
+ if (args.includes("--docker")) existingConfig.docker = true;
461
+ // Update answer with existing config
462
+ answer = {
463
+ ...answer,
464
+ backendOnly: existingConfig.backendOnly,
465
+ swaggerDocs: existingConfig.swaggerDocs,
466
+ tailwindcss: existingConfig.tailwindcss,
467
+ websocket: existingConfig.websocket,
468
+ mcp: existingConfig.mcp,
469
+ prisma: existingConfig.prisma,
470
+ docker: existingConfig.docker,
471
+ };
472
+ // Set up as an update
473
+ let excludeFiles = [];
474
+ existingConfig.excludeFiles?.map((file) => {
475
+ const filePath = path.join(projectPath, file);
476
+ if (fs.existsSync(filePath))
477
+ excludeFiles.push(filePath.replace(/\\/g, "/"));
478
+ });
479
+ updateAnswer = {
480
+ ...answer,
481
+ isUpdate: true,
482
+ excludeFiles: existingConfig.excludeFiles ?? [],
483
+ excludeFilePath: excludeFiles ?? [],
484
+ filePath: projectPath,
485
+ };
156
486
  }
157
- else {
158
- execSync("npm install -g create-prisma-php-app", { stdio: "inherit" });
487
+ } else {
488
+ // Regular project handling (existing logic)
489
+ const configPath = path.join(currentDir, "prisma-php.json");
490
+ const projectNamePath = path.join(currentDir, projectName);
491
+ const projectNameConfigPath = path.join(
492
+ projectNamePath,
493
+ "prisma-php.json"
494
+ );
495
+ if (fs.existsSync(configPath)) {
496
+ projectPath = currentDir;
497
+ } else if (
498
+ fs.existsSync(projectNamePath) &&
499
+ fs.existsSync(projectNameConfigPath)
500
+ ) {
501
+ projectPath = projectNamePath;
502
+ process.chdir(projectNamePath);
503
+ } else {
504
+ if (!fs.existsSync(projectNamePath)) {
505
+ fs.mkdirSync(projectNamePath, { recursive: true });
506
+ }
507
+ projectPath = projectNamePath;
508
+ process.chdir(projectNamePath);
159
509
  }
160
- // Create the project directory
161
- if (!projectName)
162
- fs.mkdirSync(answer.projectName);
163
- const currentDir = process.cwd();
164
- let projectPath = projectName
165
- ? currentDir
166
- : path.join(currentDir, answer.projectName);
167
- if (!projectName)
168
- process.chdir(answer.projectName);
169
- let npmDependencies = [
170
- npmPkg("typescript"),
171
- npmPkg("@types/node"),
172
- npmPkg("tsx"),
173
- npmPkg("http-proxy-middleware"),
174
- npmPkg("chalk"),
175
- npmPkg("npm-run-all"),
176
- npmPkg("browser-sync"),
177
- npmPkg("@types/browser-sync"),
178
- npmPkg("php-parser"),
179
- ];
180
- let composerDependencies = [
181
- composerPkg("vlucas/phpdotenv"),
182
- composerPkg("firebase/php-jwt"),
183
- composerPkg("phpmailer/phpmailer"),
184
- composerPkg("guzzlehttp/guzzle"),
185
- composerPkg("ezyang/htmlpurifier"),
186
- composerPkg("symfony/uid"),
187
- composerPkg("brick/math"),
188
- composerPkg("tsnc/prisma-php"),
189
- ];
190
- if (answer.swaggerDocs) {
191
- npmDependencies.push(npmPkg("swagger-jsdoc"), npmPkg("@types/swagger-jsdoc"));
510
+ }
511
+ } else {
512
+ // Interactive mode
513
+ fs.mkdirSync(answer.projectName, { recursive: true });
514
+ projectPath = path.join(currentDir, answer.projectName);
515
+ process.chdir(answer.projectName);
516
+ }
517
+ let npmDependencies = [
518
+ npmPkg("typescript"),
519
+ npmPkg("@types/node"),
520
+ npmPkg("tsx"),
521
+ npmPkg("http-proxy-middleware"),
522
+ npmPkg("chalk"),
523
+ npmPkg("npm-run-all"),
524
+ npmPkg("browser-sync"),
525
+ npmPkg("@types/browser-sync"),
526
+ npmPkg("php-parser"),
527
+ ];
528
+ let composerDependencies = [
529
+ composerPkg("vlucas/phpdotenv"),
530
+ composerPkg("firebase/php-jwt"),
531
+ composerPkg("phpmailer/phpmailer"),
532
+ composerPkg("guzzlehttp/guzzle"),
533
+ composerPkg("ezyang/htmlpurifier"),
534
+ composerPkg("symfony/uid"),
535
+ composerPkg("brick/math"),
536
+ // composerPkg("tsnc/prisma-php"),
537
+ ];
538
+ if (answer.swaggerDocs) {
539
+ npmDependencies.push(
540
+ npmPkg("swagger-jsdoc"),
541
+ npmPkg("@types/swagger-jsdoc")
542
+ );
543
+ }
544
+ if (answer.swaggerDocs && answer.prisma) {
545
+ npmDependencies.push(npmPkg("prompts"), npmPkg("@types/prompts"));
546
+ }
547
+ if (answer.tailwindcss) {
548
+ npmDependencies.push(
549
+ npmPkg("tailwindcss"),
550
+ npmPkg("postcss"),
551
+ npmPkg("postcss-cli"),
552
+ npmPkg("@tailwindcss/postcss"),
553
+ npmPkg("cssnano")
554
+ );
555
+ }
556
+ if (answer.websocket) {
557
+ composerDependencies.push("cboden/ratchet");
558
+ }
559
+ if (answer.mcp) {
560
+ composerDependencies.push("php-mcp/server");
561
+ }
562
+ if (answer.prisma) {
563
+ execSync("npm install -g prisma-client-php", { stdio: "inherit" });
564
+ }
565
+ // Only setup starter kit if it's not already done
566
+ if (answer.starterKit && !isStarterKitProject) {
567
+ await setupStarterKit(projectPath, answer);
568
+ }
569
+ await installNpmDependencies(projectPath, npmDependencies, true);
570
+ await installComposerDependencies(projectPath, composerDependencies);
571
+ if (!projectName) {
572
+ execSync("npx tsc --init", { stdio: "inherit" });
573
+ }
574
+ await createDirectoryStructure(projectPath, answer);
575
+ if (answer.prisma) {
576
+ execSync("npx ppo init --prisma-php", { stdio: "inherit" });
577
+ }
578
+ if (answer.swaggerDocs) {
579
+ const swaggerDocsPath = path.join(
580
+ projectPath,
581
+ "src",
582
+ "app",
583
+ "swagger-docs"
584
+ );
585
+ // Check if the directory exists
586
+ if (fs.existsSync(swaggerDocsPath)) {
587
+ // If it exists and is not empty, remove it before cloning
588
+ if (fs.readdirSync(swaggerDocsPath).length > 0) {
589
+ console.log("Removing existing swagger-docs directory...");
590
+ fs.rmSync(swaggerDocsPath, { recursive: true, force: true });
192
591
  }
193
- if (answer.swaggerDocs && answer.prisma) {
194
- npmDependencies.push(npmPkg("prompts"), npmPkg("@types/prompts"));
592
+ }
593
+ // Clone the Git repository into the swagger-docs directory
594
+ execSync(
595
+ `git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${swaggerDocsPath}`,
596
+ { stdio: "inherit" }
597
+ );
598
+ // delete the folder .git
599
+ fs.rmSync(path.join(swaggerDocsPath, ".git"), {
600
+ recursive: true,
601
+ force: true,
602
+ });
603
+ }
604
+ if (updateAnswer?.isUpdate) {
605
+ const updateUninstallNpmDependencies = [];
606
+ const updateUninstallComposerDependencies = [];
607
+ // Helper function to check if a composer package is installed
608
+ const isComposerPackageInstalled = (packageName) => {
609
+ try {
610
+ const composerJsonPath = path.join(projectPath, "composer.json");
611
+ if (fs.existsSync(composerJsonPath)) {
612
+ const composerJson = JSON.parse(
613
+ fs.readFileSync(composerJsonPath, "utf8")
614
+ );
615
+ return !!(
616
+ composerJson.require && composerJson.require[packageName]
617
+ );
618
+ }
619
+ return false;
620
+ } catch {
621
+ return false;
622
+ }
623
+ };
624
+ // Helper function to check if an npm package is installed
625
+ const isNpmPackageInstalled = (packageName) => {
626
+ try {
627
+ const packageJsonPath = path.join(projectPath, "package.json");
628
+ if (fs.existsSync(packageJsonPath)) {
629
+ const packageJson = JSON.parse(
630
+ fs.readFileSync(packageJsonPath, "utf8")
631
+ );
632
+ return !!(
633
+ (packageJson.dependencies &&
634
+ packageJson.dependencies[packageName]) ||
635
+ (packageJson.devDependencies &&
636
+ packageJson.devDependencies[packageName])
637
+ );
638
+ }
639
+ return false;
640
+ } catch {
641
+ return false;
195
642
  }
196
- if (answer.tailwindcss) {
197
- npmDependencies.push(npmPkg("tailwindcss"), npmPkg("postcss"), npmPkg("postcss-cli"), npmPkg("@tailwindcss/postcss"), npmPkg("cssnano"));
643
+ };
644
+ if (updateAnswer.backendOnly) {
645
+ nonBackendFiles.forEach((file) => {
646
+ const filePath = path.join(projectPath, "src", "app", file);
647
+ if (fs.existsSync(filePath)) {
648
+ fs.unlinkSync(filePath);
649
+ console.log(`${file} was deleted successfully.`);
650
+ }
651
+ });
652
+ const backendOnlyFolders = ["js", "css"];
653
+ backendOnlyFolders.forEach((folder) => {
654
+ const folderPath = path.join(projectPath, "src", "app", folder);
655
+ if (fs.existsSync(folderPath)) {
656
+ fs.rmSync(folderPath, { recursive: true, force: true });
657
+ console.log(`${folder} was deleted successfully.`);
658
+ }
659
+ });
660
+ }
661
+ if (!updateAnswer.swaggerDocs) {
662
+ const swaggerDocsFolder = path.join(
663
+ projectPath,
664
+ "src",
665
+ "app",
666
+ "swagger-docs"
667
+ );
668
+ if (fs.existsSync(swaggerDocsFolder)) {
669
+ fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
670
+ console.log(`swagger-docs was deleted successfully.`);
198
671
  }
199
- if (answer.websocket) {
200
- npmDependencies.push(npmPkg("chokidar-cli"));
201
- composerDependencies.push("cboden/ratchet");
672
+ const swaggerFiles = ["swagger-config.ts"];
673
+ swaggerFiles.forEach((file) => {
674
+ const filePath = path.join(projectPath, "settings", file);
675
+ if (fs.existsSync(filePath)) {
676
+ fs.unlinkSync(filePath);
677
+ console.log(`${file} was deleted successfully.`);
678
+ }
679
+ });
680
+ // Only add to uninstall list if packages are actually installed
681
+ if (isNpmPackageInstalled("swagger-jsdoc")) {
682
+ updateUninstallNpmDependencies.push("swagger-jsdoc");
202
683
  }
203
- if (answer.prisma) {
204
- execSync("npm install -g prisma-client-php", { stdio: "inherit" });
684
+ if (isNpmPackageInstalled("@types/swagger-jsdoc")) {
685
+ updateUninstallNpmDependencies.push("@types/swagger-jsdoc");
205
686
  }
206
- await installNpmDependencies(projectPath, npmDependencies, true);
207
- await installComposerDependencies(projectPath, composerDependencies);
208
- if (!projectName) {
209
- execSync("npx tsc --init", { stdio: "inherit" });
687
+ if (isNpmPackageInstalled("prompts")) {
688
+ updateUninstallNpmDependencies.push("prompts");
210
689
  }
211
- await createDirectoryStructure(projectPath, answer);
212
- if (answer.prisma) {
213
- execSync("npx ppo init --prisma-php", { stdio: "inherit" });
690
+ if (isNpmPackageInstalled("@types/prompts")) {
691
+ updateUninstallNpmDependencies.push("@types/prompts");
214
692
  }
215
- if (answer.swaggerDocs) {
216
- const swaggerDocsPath = path.join(projectPath, "src", "app", "swagger-docs");
217
- // Check if the directory exists
218
- if (fs.existsSync(swaggerDocsPath)) {
219
- // If it exists and is not empty, remove it before cloning
220
- if (fs.readdirSync(swaggerDocsPath).length > 0) {
221
- console.log("Removing existing swagger-docs directory...");
222
- fs.rmSync(swaggerDocsPath, { recursive: true, force: true });
223
- }
224
- }
225
- // Clone the Git repository into the swagger-docs directory
226
- execSync(`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${swaggerDocsPath}`, { stdio: "inherit" });
227
- // delete the folder .git
228
- fs.rmSync(path.join(swaggerDocsPath, ".git"), {
229
- recursive: true,
230
- force: true,
231
- });
693
+ }
694
+ if (!updateAnswer.tailwindcss) {
695
+ const tailwindFiles = ["postcss.config.js"];
696
+ tailwindFiles.forEach((file) => {
697
+ const filePath = path.join(projectPath, file);
698
+ if (fs.existsSync(filePath)) {
699
+ fs.unlinkSync(filePath);
700
+ console.log(`${file} was deleted successfully.`);
701
+ }
702
+ });
703
+ // Only add to uninstall list if packages are actually installed
704
+ const tailwindPackages = [
705
+ "tailwindcss",
706
+ "postcss",
707
+ "postcss-cli",
708
+ "@tailwindcss/postcss",
709
+ "cssnano",
710
+ ];
711
+ tailwindPackages.forEach((pkg) => {
712
+ if (isNpmPackageInstalled(pkg)) {
713
+ updateUninstallNpmDependencies.push(pkg);
714
+ }
715
+ });
716
+ }
717
+ if (!updateAnswer.websocket) {
718
+ const websocketFiles = ["restart-websocket.ts"];
719
+ websocketFiles.forEach((file) => {
720
+ const filePath = path.join(projectPath, "settings", file);
721
+ if (fs.existsSync(filePath)) {
722
+ fs.unlinkSync(filePath);
723
+ console.log(`${file} was deleted successfully.`);
724
+ }
725
+ });
726
+ const websocketFolder = path.join(
727
+ projectPath,
728
+ "src",
729
+ "Lib",
730
+ "Websocket"
731
+ );
732
+ if (fs.existsSync(websocketFolder)) {
733
+ fs.rmSync(websocketFolder, { recursive: true, force: true });
734
+ console.log(`Websocket folder was deleted successfully.`);
232
735
  }
233
- if (updateAnswer?.isUpdate) {
234
- const updateUninstallNpmDependencies = [];
235
- const updateUninstallComposerDependencies = [];
236
- if (updateAnswer.backendOnly) {
237
- nonBackendFiles.forEach((file) => {
238
- const filePath = path.join(projectPath, "src", "app", file);
239
- if (fs.existsSync(filePath)) {
240
- fs.unlinkSync(filePath); // Delete each file if it exists
241
- console.log(`${file} was deleted successfully.`);
242
- }
243
- else {
244
- console.log(`${file} does not exist.`);
245
- }
246
- });
247
- const backendOnlyFolders = ["js", "css"];
248
- backendOnlyFolders.forEach((folder) => {
249
- const folderPath = path.join(projectPath, "src", "app", folder);
250
- if (fs.existsSync(folderPath)) {
251
- fs.rmSync(folderPath, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
252
- console.log(`${folder} was deleted successfully.`);
253
- }
254
- else {
255
- console.log(`${folder} does not exist.`);
256
- }
257
- });
258
- }
259
- if (!updateAnswer.swaggerDocs) {
260
- const swaggerDocsFolder = path.join(projectPath, "src", "app", "swagger-docs");
261
- if (fs.existsSync(swaggerDocsFolder)) {
262
- fs.rmSync(swaggerDocsFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
263
- console.log(`swagger-docs was deleted successfully.`);
264
- }
265
- const swaggerFiles = ["swagger-config.ts"];
266
- swaggerFiles.forEach((file) => {
267
- const filePath = path.join(projectPath, "settings", file);
268
- if (fs.existsSync(filePath)) {
269
- fs.unlinkSync(filePath); // Delete each file if it exists
270
- console.log(`${file} was deleted successfully.`);
271
- }
272
- else {
273
- console.log(`${file} does not exist.`);
274
- }
275
- });
276
- updateUninstallNpmDependencies.push("swagger-jsdoc", "@types/swagger-jsdoc", "prompts", "@types/prompts");
277
- }
278
- if (!updateAnswer.tailwindcss) {
279
- const tailwindFiles = ["postcss.config.js"];
280
- tailwindFiles.forEach((file) => {
281
- const filePath = path.join(projectPath, file);
282
- if (fs.existsSync(filePath)) {
283
- fs.unlinkSync(filePath); // Delete each file if it exists
284
- console.log(`${file} was deleted successfully.`);
285
- }
286
- else {
287
- console.log(`${file} does not exist.`);
288
- }
289
- });
290
- updateUninstallNpmDependencies.push("tailwindcss", "postcss", "postcss-cli", "@tailwindcss/postcss", "cssnano");
291
- }
292
- if (!updateAnswer.websocket) {
293
- const websocketFiles = [
294
- "restart-websocket.ts",
295
- "restart-websocket.bat",
296
- ];
297
- websocketFiles.forEach((file) => {
298
- const filePath = path.join(projectPath, "settings", file);
299
- if (fs.existsSync(filePath)) {
300
- fs.unlinkSync(filePath); // Delete each file if it exists
301
- console.log(`${file} was deleted successfully.`);
302
- }
303
- else {
304
- console.log(`${file} does not exist.`);
305
- }
306
- });
307
- const websocketFolder = path.join(projectPath, "src", "Websocket");
308
- if (fs.existsSync(websocketFolder)) {
309
- fs.rmSync(websocketFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
310
- console.log(`Websocket folder was deleted successfully.`);
311
- }
312
- const websocketServerFile = path.join(projectPath, "websocket-server.php");
313
- if (fs.existsSync(websocketServerFile)) {
314
- fs.unlinkSync(websocketServerFile); // Delete the file if it exists
315
- console.log(`websocket-server.php was deleted successfully.`);
316
- }
317
- updateUninstallNpmDependencies.push("chokidar-cli");
318
- updateUninstallComposerDependencies.push("cboden/ratchet");
319
- }
320
- if (!updateAnswer.prisma) {
321
- updateUninstallNpmDependencies.push("prisma", "@prisma/client", "@prisma/internals");
322
- }
323
- if (!updateAnswer.docker) {
324
- const dockerFiles = [
325
- ".dockerignore",
326
- "docker-compose.yml",
327
- "Dockerfile",
328
- "apache.conf",
329
- ];
330
- dockerFiles.forEach((file) => {
331
- const filePath = path.join(projectPath, file);
332
- if (fs.existsSync(filePath)) {
333
- fs.unlinkSync(filePath); // Delete each file if it exists
334
- console.log(`${file} was deleted successfully.`);
335
- }
336
- else {
337
- console.log(`${file} does not exist.`);
338
- }
339
- });
340
- }
341
- if (updateUninstallNpmDependencies.length > 0) {
342
- await uninstallNpmDependencies(projectPath, updateUninstallNpmDependencies, true);
343
- }
344
- if (updateUninstallComposerDependencies.length > 0) {
345
- await uninstallComposerDependencies(projectPath, updateUninstallComposerDependencies);
346
- }
736
+ // composer package for websocket only
737
+ if (isComposerPackageInstalled("cboden/ratchet")) {
738
+ updateUninstallComposerDependencies.push("cboden/ratchet");
347
739
  }
348
- const projectPathModified = projectPath.replace(/\\/g, "\\");
349
- const bsConfig = bsConfigUrls(projectPathModified);
350
- const prismaPhpConfig = {
351
- projectName: answer.projectName,
352
- projectRootPath: projectPathModified,
353
- phpEnvironment: "XAMPP",
354
- phpRootPathExe: "C:\\xampp\\php\\php.exe",
355
- bsTarget: bsConfig.bsTarget,
356
- bsPathRewrite: bsConfig.bsPathRewrite,
357
- backendOnly: answer.backendOnly,
358
- swaggerDocs: answer.swaggerDocs,
359
- tailwindcss: answer.tailwindcss,
360
- websocket: answer.websocket,
361
- prisma: answer.prisma,
362
- docker: answer.docker,
363
- version: latestVersionOfCreatePrismaPhpApp,
364
- excludeFiles: updateAnswer?.excludeFiles ?? [],
365
- };
366
- fs.writeFileSync(path.join(projectPath, "prisma-php.json"), JSON.stringify(prismaPhpConfig, null, 2), { flag: "w" });
367
- if (updateAnswer?.isUpdate) {
368
- execSync("C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update", {
369
- stdio: "inherit",
370
- });
740
+ }
741
+ if (!updateAnswer.mcp) {
742
+ const mcpFiles = ["restart-mcp.ts"];
743
+ mcpFiles.forEach((file) => {
744
+ const filePath = path.join(projectPath, "settings", file);
745
+ if (fs.existsSync(filePath)) {
746
+ fs.unlinkSync(filePath);
747
+ console.log(`${file} was deleted successfully.`);
748
+ }
749
+ });
750
+ const mcpFolder = path.join(projectPath, "src", "Lib", "MCP");
751
+ if (fs.existsSync(mcpFolder)) {
752
+ fs.rmSync(mcpFolder, { recursive: true, force: true });
753
+ console.log(`MCP folder was deleted successfully.`);
371
754
  }
372
- else {
373
- execSync("C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install", {
374
- stdio: "inherit",
375
- });
755
+ // composer package for MCP only
756
+ if (isComposerPackageInstalled("php-mcp/server")) {
757
+ updateUninstallComposerDependencies.push("php-mcp/server");
376
758
  }
377
- console.log("\n=========================\n");
378
- console.log(`${chalk.green("Success!")} Prisma PHP project successfully created in ${chalk.green(`${currentDir.replace(/\\/g, "/")}/${answer.projectName}`)}!`);
379
- console.log("\n=========================");
759
+ }
760
+ if (!updateAnswer.prisma) {
761
+ const prismaPackages = [
762
+ "prisma",
763
+ "@prisma/client",
764
+ "@prisma/internals",
765
+ ];
766
+ prismaPackages.forEach((pkg) => {
767
+ if (isNpmPackageInstalled(pkg)) {
768
+ updateUninstallNpmDependencies.push(pkg);
769
+ }
770
+ });
771
+ }
772
+ if (!updateAnswer.docker) {
773
+ const dockerFiles = [
774
+ ".dockerignore",
775
+ "docker-compose.yml",
776
+ "Dockerfile",
777
+ "apache.conf",
778
+ ];
779
+ dockerFiles.forEach((file) => {
780
+ const filePath = path.join(projectPath, file);
781
+ if (fs.existsSync(filePath)) {
782
+ fs.unlinkSync(filePath);
783
+ console.log(`${file} was deleted successfully.`);
784
+ }
785
+ });
786
+ }
787
+ // Only uninstall if there are packages to uninstall
788
+ const uniq = (arr) => Array.from(new Set(arr));
789
+ const npmToUninstall = uniq(updateUninstallNpmDependencies);
790
+ const composerToUninstall = uniq(updateUninstallComposerDependencies);
791
+ if (npmToUninstall.length > 0) {
792
+ console.log(`Uninstalling npm packages: ${npmToUninstall.join(", ")}`);
793
+ await uninstallNpmDependencies(projectPath, npmToUninstall, true);
794
+ }
795
+ if (composerToUninstall.length > 0) {
796
+ console.log(
797
+ `Uninstalling composer packages: ${composerToUninstall.join(", ")}`
798
+ );
799
+ await uninstallComposerDependencies(projectPath, composerToUninstall);
800
+ }
801
+ }
802
+ // Skip creating prismaPhpConfig if it's a starter kit project that already has one
803
+ if (
804
+ !isStarterKitProject ||
805
+ !fs.existsSync(path.join(projectPath, "prisma-php.json"))
806
+ ) {
807
+ // Create prisma-php.json with all the existing logic
808
+ const projectPathModified = projectPath.replace(/\\/g, "\\");
809
+ const bsConfig = bsConfigUrls(projectPathModified);
810
+ const prismaPhpConfig = {
811
+ projectName: answer.projectName,
812
+ projectRootPath: projectPathModified,
813
+ phpEnvironment: "XAMPP",
814
+ phpRootPathExe: "C:\\xampp\\php\\php.exe",
815
+ bsTarget: bsConfig.bsTarget,
816
+ bsPathRewrite: bsConfig.bsPathRewrite,
817
+ backendOnly: answer.backendOnly,
818
+ swaggerDocs: answer.swaggerDocs,
819
+ tailwindcss: answer.tailwindcss,
820
+ websocket: answer.websocket,
821
+ mcp: answer.mcp,
822
+ prisma: answer.prisma,
823
+ docker: answer.docker,
824
+ version: latestVersionOfCreatePrismaPhpApp,
825
+ excludeFiles: updateAnswer?.excludeFiles ?? [],
826
+ };
827
+ fs.writeFileSync(
828
+ path.join(projectPath, "prisma-php.json"),
829
+ JSON.stringify(prismaPhpConfig, null, 2),
830
+ { flag: "w" }
831
+ );
380
832
  }
381
- catch (error) {
382
- console.error("Error while creating the project:", error);
383
- process.exit(1);
833
+ if (updateAnswer?.isUpdate) {
834
+ execSync(
835
+ "C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update",
836
+ {
837
+ stdio: "inherit",
838
+ }
839
+ );
840
+ } else {
841
+ execSync(
842
+ "C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install",
843
+ {
844
+ stdio: "inherit",
845
+ }
846
+ );
384
847
  }
848
+ console.log("\n=========================\n");
849
+ console.log(
850
+ `${chalk.green(
851
+ "Success!"
852
+ )} Prisma PHP project successfully created in ${chalk.green(
853
+ projectPath.replace(/\\/g, "/")
854
+ )}!`
855
+ );
856
+ console.log("\n=========================");
857
+ } catch (error) {
858
+ console.error("Error while creating the project:", error);
859
+ process.exit(1);
860
+ }
385
861
  }
386
862
  main();